{
  "openapi": "3.1.0",
  "info": {
    "title": "Vitrina API",
    "version": "17.37.0",
    "description": "Public API for managing AI agents, tools, conversations, tickets, and integrations. All endpoints live under `/api/v1` and require a `Bearer <token>` (either an `sk_*` API key or a Supabase JWT). State-changing endpoints accept the optional `Idempotency-Key` header."
  },
  "servers": [
    {
      "url": "/api/v1"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Either an `sk_*` API key or a Supabase JWT."
      },
      "PublishableKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A browser-safe `pk_*` publishable widget key (ADR 0033), origin-locked, fixed scopes stock:read+leads:intake+widget:chat+appointments:intake."
      },
      "DeviceAgentBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A `va_*` per-agent token minted by `POST /device-agent/claim` — identifies exactly one device_agent row, never a tenant credential."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {},
              "field_errors": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "requestId": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "Author": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "member",
              "api_key",
              "ai_agent",
              "system",
              "contact"
            ],
            "description": "`member` = a workspace member (in the app, or through a personal token / connected app — see `via`); `api_key` = an API key, a workspace integration credential («CRM propio (API)»); `ai_agent` = the AI agent; `system` = the platform itself; `contact` = the customer."
          },
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member's user id, the API key id, the AI agent id or the contact id. Null for `system` and for a contact not yet identified. Branch on `kind` + `id`, never on `name`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name. For an API key it is the key's name AS IT WAS when it acted (renaming or revoking the key does not rewrite history); for a member, their current profile name. Null when unknown."
          },
          "via": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "connected_app",
                  "personal_token"
                ],
                "description": "The credential a member acted through."
              },
              "name": {
                "type": "string",
                "description": "The connected app's name («Claude») or the personal token's name. May be empty when unresolvable."
              }
            },
            "required": [
              "kind",
              "name"
            ],
            "description": "Present when a MEMBER acted through a connected app or a personal token: the member is still the author, with the app recorded beside them («Camila vía Claude»)."
          }
        },
        "required": [
          "kind",
          "id",
          "name"
        ]
      },
      "NotificationTarget": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "record"
                ]
              },
              "record": {
                "type": "string",
                "enum": [
                  "conversation",
                  "contact",
                  "lead",
                  "busqueda",
                  "appointment",
                  "ai_agent",
                  "price_approval",
                  "cotizacion",
                  "bank_movement",
                  "vehicle",
                  "whatsapp_flow",
                  "campaign",
                  "negocio"
                ]
              },
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "tab": {
                "type": "string"
              },
              "sub": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "kind",
              "record",
              "id"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "list"
                ]
              },
              "screen": {
                "type": "string",
                "enum": [
                  "conversations",
                  "busquedas",
                  "contacts_demand",
                  "clinic_call_today",
                  "clinic_inventory",
                  "transferencias",
                  "pipelines",
                  "price_approvals",
                  "campaigns"
                ]
              },
              "filter": {
                "type": "object",
                "additionalProperties": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    }
                  ]
                }
              }
            },
            "required": [
              "kind",
              "screen",
              "filter"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "page"
                ]
              },
              "page": {
                "type": "string",
                "enum": [
                  "settings.channels",
                  "settings.channel",
                  "settings.webhooks",
                  "finanzas.bancos",
                  "finanzas.flujo_de_caja",
                  "finanzas.f29",
                  "finanzas.contraparte",
                  "settings.api_keys",
                  "settings.billing",
                  "settings.seguimientos",
                  "settings.templates",
                  "settings.marketplaces",
                  "settings.notifications",
                  "settings.payment_methods",
                  "ai_agents",
                  "ads",
                  "onboarding",
                  "vehicles.marketplaces",
                  "calendar"
                ]
              },
              "params": {
                "type": "object",
                "additionalProperties": {
                  "anyOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "number"
                    },
                    {
                      "type": "boolean"
                    }
                  ]
                }
              }
            },
            "required": [
              "kind",
              "page"
            ]
          }
        ],
        "description": "Where the notification leads — a typed destination, never a URL. `record` = one subject (a conversation, a lead, an appointment…), `list` = a filtered list (e.g. the Búsquedas a daily summary counted), `page` = a fixed screen (e.g. one channel’s settings). The UI routes each kind with one table."
      },
      "Notification": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "description": "Registry key, e.g. `assigned`, `busqueda.daily_summary`."
          },
          "category": {
            "type": "string"
          },
          "actor_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "entity_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "entity_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "Render snapshot; the UI renders copy from `type` + `data`."
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotificationTarget"
              },
              {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "record"
                        ]
                      },
                      "record": {
                        "type": "string",
                        "enum": [
                          "conversation",
                          "contact",
                          "lead",
                          "busqueda",
                          "appointment",
                          "ai_agent",
                          "price_approval",
                          "cotizacion",
                          "bank_movement",
                          "vehicle",
                          "whatsapp_flow",
                          "campaign",
                          "negocio"
                        ]
                      },
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "tab": {
                        "type": "string"
                      },
                      "sub": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "required": [
                      "kind",
                      "record",
                      "id"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "list"
                        ]
                      },
                      "screen": {
                        "type": "string",
                        "enum": [
                          "conversations",
                          "busquedas",
                          "contacts_demand",
                          "clinic_call_today",
                          "clinic_inventory",
                          "transferencias",
                          "pipelines",
                          "price_approvals",
                          "campaigns"
                        ]
                      },
                      "filter": {
                        "type": "object",
                        "additionalProperties": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      }
                    },
                    "required": [
                      "kind",
                      "screen",
                      "filter"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "page"
                        ]
                      },
                      "page": {
                        "type": "string",
                        "enum": [
                          "settings.channels",
                          "settings.channel",
                          "settings.webhooks",
                          "finanzas.bancos",
                          "finanzas.flujo_de_caja",
                          "finanzas.f29",
                          "finanzas.contraparte",
                          "settings.api_keys",
                          "settings.billing",
                          "settings.seguimientos",
                          "settings.templates",
                          "settings.marketplaces",
                          "settings.notifications",
                          "settings.payment_methods",
                          "ai_agents",
                          "ads",
                          "onboarding",
                          "vehicles.marketplaces",
                          "calendar"
                        ]
                      },
                      "params": {
                        "type": "object",
                        "additionalProperties": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      }
                    },
                    "required": [
                      "kind",
                      "page"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            ]
          },
          "secondary_target": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotificationTarget"
              },
              {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "record"
                        ]
                      },
                      "record": {
                        "type": "string",
                        "enum": [
                          "conversation",
                          "contact",
                          "lead",
                          "busqueda",
                          "appointment",
                          "ai_agent",
                          "price_approval",
                          "cotizacion",
                          "bank_movement",
                          "vehicle",
                          "whatsapp_flow",
                          "campaign",
                          "negocio"
                        ]
                      },
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "tab": {
                        "type": "string"
                      },
                      "sub": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "required": [
                      "kind",
                      "record",
                      "id"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "list"
                        ]
                      },
                      "screen": {
                        "type": "string",
                        "enum": [
                          "conversations",
                          "busquedas",
                          "contacts_demand",
                          "clinic_call_today",
                          "clinic_inventory",
                          "transferencias",
                          "pipelines",
                          "price_approvals",
                          "campaigns"
                        ]
                      },
                      "filter": {
                        "type": "object",
                        "additionalProperties": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      }
                    },
                    "required": [
                      "kind",
                      "screen",
                      "filter"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "page"
                        ]
                      },
                      "page": {
                        "type": "string",
                        "enum": [
                          "settings.channels",
                          "settings.channel",
                          "settings.webhooks",
                          "finanzas.bancos",
                          "finanzas.flujo_de_caja",
                          "finanzas.f29",
                          "finanzas.contraparte",
                          "settings.api_keys",
                          "settings.billing",
                          "settings.seguimientos",
                          "settings.templates",
                          "settings.marketplaces",
                          "settings.notifications",
                          "settings.payment_methods",
                          "ai_agents",
                          "ads",
                          "onboarding",
                          "vehicles.marketplaces",
                          "calendar"
                        ]
                      },
                      "params": {
                        "type": "object",
                        "additionalProperties": {
                          "anyOf": [
                            {
                              "type": "string"
                            },
                            {
                              "type": "number"
                            },
                            {
                              "type": "boolean"
                            }
                          ]
                        }
                      }
                    },
                    "required": [
                      "kind",
                      "page"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            ]
          },
          "live": {
            "type": "boolean",
            "description": "A standing condition: refreshed in place, marked read when it ends."
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "category",
          "actor_id",
          "entity_type",
          "entity_id",
          "data",
          "target",
          "secondary_target",
          "live",
          "read_at",
          "created_at"
        ]
      },
      "NotificationPreferenceCatalog": {
        "type": "object",
        "properties": {
          "preset": {
            "type": "string",
            "enum": [
              "important",
              "all",
              "custom"
            ]
          },
          "push_enabled": {
            "type": "boolean"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "inbox",
                "agenda",
                "sales",
                "followups",
                "approvals",
                "payments",
                "agents",
                "integrations",
                "finanzas",
                "safety",
                "account"
              ]
            }
          },
          "types": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "category": {
                  "type": "string",
                  "enum": [
                    "inbox",
                    "agenda",
                    "sales",
                    "followups",
                    "approvals",
                    "payments",
                    "agents",
                    "integrations",
                    "finanzas",
                    "safety",
                    "account"
                  ]
                },
                "slug": {
                  "type": "string",
                  "description": "i18n slug: the UI renders `notif.<slug>.title` / `.when`."
                },
                "mandatory": {
                  "type": "boolean"
                },
                "locked_reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "bell_mutable": {
                  "type": "boolean"
                },
                "delivery": {
                  "type": "string",
                  "enum": [
                    "immediate",
                    "live",
                    "digest"
                  ]
                },
                "channels": {
                  "type": "object",
                  "properties": {
                    "in_app": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "unavailable",
                            "locked",
                            "always",
                            "on",
                            "off"
                          ],
                          "description": "`unavailable` = not sent this way (or WhatsApp not armed by the workspace); `locked` = mandatory; `always` = the bell of a type you must act on; `on` / `off` = your choice, or the default when you never chose."
                        },
                        "default": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "on",
                            "off",
                            "locked",
                            "workspace"
                          ]
                        }
                      },
                      "required": [
                        "state",
                        "default"
                      ]
                    },
                    "email": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "unavailable",
                            "locked",
                            "always",
                            "on",
                            "off"
                          ],
                          "description": "`unavailable` = not sent this way (or WhatsApp not armed by the workspace); `locked` = mandatory; `always` = the bell of a type you must act on; `on` / `off` = your choice, or the default when you never chose."
                        },
                        "default": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "on",
                            "off",
                            "locked",
                            "workspace"
                          ]
                        }
                      },
                      "required": [
                        "state",
                        "default"
                      ]
                    },
                    "push": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "unavailable",
                            "locked",
                            "always",
                            "on",
                            "off"
                          ],
                          "description": "`unavailable` = not sent this way (or WhatsApp not armed by the workspace); `locked` = mandatory; `always` = the bell of a type you must act on; `on` / `off` = your choice, or the default when you never chose."
                        },
                        "default": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "on",
                            "off",
                            "locked",
                            "workspace"
                          ]
                        }
                      },
                      "required": [
                        "state",
                        "default"
                      ]
                    },
                    "whatsapp": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "unavailable",
                            "locked",
                            "always",
                            "on",
                            "off"
                          ],
                          "description": "`unavailable` = not sent this way (or WhatsApp not armed by the workspace); `locked` = mandatory; `always` = the bell of a type you must act on; `on` / `off` = your choice, or the default when you never chose."
                        },
                        "default": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "on",
                            "off",
                            "locked",
                            "workspace"
                          ]
                        }
                      },
                      "required": [
                        "state",
                        "default"
                      ]
                    }
                  },
                  "required": [
                    "in_app",
                    "email",
                    "push",
                    "whatsapp"
                  ]
                }
              },
              "required": [
                "key",
                "category",
                "slug",
                "mandatory",
                "locked_reason",
                "bell_mutable",
                "delivery",
                "channels"
              ]
            }
          }
        },
        "required": [
          "preset",
          "push_enabled",
          "categories",
          "types"
        ]
      },
      "NotificationPreferencesUpdate": {
        "type": "object",
        "properties": {
          "preset": {
            "type": "string",
            "enum": [
              "important",
              "all",
              "custom"
            ],
            "description": "`important` = back to the defaults (clears your per-type choices); `all` = every switchable cell on; `custom` = keep your cells."
          },
          "changes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 80
                },
                "channel": {
                  "type": "string",
                  "enum": [
                    "in_app",
                    "email",
                    "push",
                    "whatsapp"
                  ]
                },
                "enabled": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "channel",
                "enabled"
              ]
            },
            "maxItems": 200
          },
          "push_enabled": {
            "type": "boolean"
          }
        }
      },
      "TenantSettingsPublic": {
        "type": "object",
        "properties": {
          "trade_in": {
            "type": "object",
            "properties": {
              "price_table": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "model": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80
                    },
                    "top": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "used": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "min_battery_pct": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "low_battery_factor": {
                      "type": "number",
                      "exclusiveMinimum": 0,
                      "maximum": 1
                    },
                    "direct_purchase": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "model",
                    "top",
                    "used"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1,
                "maxItems": 500
              },
              "not_bought": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 80
                },
                "maxItems": 200
              },
              "floor_factor": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1
              },
              "perfect_floor_factor": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1
              },
              "rounding": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000000
              },
              "perfect_battery_pct": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Z]{3}$"
              }
            },
            "required": [
              "price_table"
            ],
            "additionalProperties": false,
            "description": "Trade-in buy table («parte de pago»), for a business that buys used devices: `price_table` rows `{model, top, used, min_battery_pct?, low_battery_factor?, direct_purchase?}` (`top` = max paid for a flawless unit, `used` = with normal wear, whole currency units; capacity does not change the value; `min_battery_pct` = a known battery under it is not bought; `low_battery_factor` (0–1) = a known battery under `perfect_battery_pct` caps the price at `used × low_battery_factor` instead of `used`; `direct_purchase: false` = the model is taken only as part of the payment for another item, never bought outright — absent = true), `not_bought` (models never bought), `floor_factor` (0–1, default 0.7: the low end for a unit with issues or unknown condition is `used × floor_factor`), `perfect_floor_factor` (0–1, default 0.85: the low end for a flawless unit is `used × perfect_floor_factor`, its high end is `top`), `rounding` (default 10000), `perfect_battery_pct` (default 90), `currency` (default CLP). On an ecommerce workspace its presence offers the agents the `estimate_trade_in` tool (a wide range of what the customer would still pay) and the `quote_buyback` tool (the most paid for one device sold outright, or the discount for a `direct_purchase: false` model); the business’s own site reads it at `GET /storefront/trade-in`. Replaced whole on write; `null` clears it."
          },
          "notifications": {
            "type": "object",
            "properties": {
              "staff_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  }
                }
              },
              "unanswered_supervisor_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  },
                  "delay_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 1440,
                    "description": "Minutes of open time a conversation waits on the team before the supervisors get the WhatsApp, once per episode. Unset = 60."
                  }
                }
              },
              "reminder_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  }
                },
                "description": "Staff WhatsApp on every «sin respuesta» reminder, to the assignee (template aviso_sigue_esperando_v1). Opt-in: each send costs a WhatsApp conversation."
              },
              "booking_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "change_template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  }
                }
              },
              "visit_watchers_whatsapp": {
                "type": "object",
                "properties": {
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 5
                  }
                },
                "required": [
                  "user_ids"
                ]
              },
              "finanzas_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "digest_template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$",
                    "description": "The weekly Finanzas digest template (resumen_finanzas_semanal_v1), wired on approval."
                  }
                }
              },
              "stock_alert": {
                "type": "object",
                "properties": {
                  "stage_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 20,
                    "description": "Lead stages that trigger the «Stock faltante» check when a lead moves into them. Empty turns it off."
                  }
                },
                "required": [
                  "stage_ids"
                ]
              }
            },
            "description": "Workspace notification rails. The staff WhatsApp rails (`staff_whatsapp`, `unanswered_supervisor_whatsapp`, `booking_whatsapp`, `reminder_whatsapp`, `finanzas_whatsapp`) are opt-in: arming one (`enabled: true` with an `account_id`) submits its template to Meta on that number and wires it on approval. Each rail sent replaces that rail whole; rails not sent are kept."
          },
          "staff_rails": {
            "type": "object",
            "properties": {
              "staff_whatsapp": {
                "type": "object",
                "properties": {
                  "armed": {
                    "type": "boolean",
                    "description": "The rail is switched on (`enabled: true`)."
                  },
                  "template_state": {
                    "type": "string",
                    "enum": [
                      "off",
                      "no_number",
                      "missing",
                      "pending",
                      "approved",
                      "rejected",
                      "blocked"
                    ],
                    "description": "Worst state across the rail templates (blocked, rejected, missing, pending, approved); `off` when not armed or nothing to provision; `no_number` when armed without a sending number."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "wired": {
                    "type": "boolean",
                    "description": "The rail has its template name set (by an admin or on approval)."
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "missing",
                            "pending",
                            "approved",
                            "rejected",
                            "blocked"
                          ]
                        },
                        "rejection_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "language",
                        "state",
                        "rejection_reason"
                      ]
                    }
                  }
                },
                "required": [
                  "armed",
                  "template_state",
                  "reason",
                  "wired",
                  "templates"
                ]
              },
              "unanswered_supervisor_whatsapp": {
                "type": "object",
                "properties": {
                  "armed": {
                    "type": "boolean",
                    "description": "The rail is switched on (`enabled: true`)."
                  },
                  "template_state": {
                    "type": "string",
                    "enum": [
                      "off",
                      "no_number",
                      "missing",
                      "pending",
                      "approved",
                      "rejected",
                      "blocked"
                    ],
                    "description": "Worst state across the rail templates (blocked, rejected, missing, pending, approved); `off` when not armed or nothing to provision; `no_number` when armed without a sending number."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "wired": {
                    "type": "boolean",
                    "description": "The rail has its template name set (by an admin or on approval)."
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "missing",
                            "pending",
                            "approved",
                            "rejected",
                            "blocked"
                          ]
                        },
                        "rejection_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "language",
                        "state",
                        "rejection_reason"
                      ]
                    }
                  }
                },
                "required": [
                  "armed",
                  "template_state",
                  "reason",
                  "wired",
                  "templates"
                ]
              },
              "booking_whatsapp": {
                "type": "object",
                "properties": {
                  "armed": {
                    "type": "boolean",
                    "description": "The rail is switched on (`enabled: true`)."
                  },
                  "template_state": {
                    "type": "string",
                    "enum": [
                      "off",
                      "no_number",
                      "missing",
                      "pending",
                      "approved",
                      "rejected",
                      "blocked"
                    ],
                    "description": "Worst state across the rail templates (blocked, rejected, missing, pending, approved); `off` when not armed or nothing to provision; `no_number` when armed without a sending number."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "wired": {
                    "type": "boolean",
                    "description": "The rail has its template name set (by an admin or on approval)."
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "missing",
                            "pending",
                            "approved",
                            "rejected",
                            "blocked"
                          ]
                        },
                        "rejection_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "language",
                        "state",
                        "rejection_reason"
                      ]
                    }
                  }
                },
                "required": [
                  "armed",
                  "template_state",
                  "reason",
                  "wired",
                  "templates"
                ]
              },
              "finanzas_whatsapp": {
                "type": "object",
                "properties": {
                  "armed": {
                    "type": "boolean",
                    "description": "The rail is switched on (`enabled: true`)."
                  },
                  "template_state": {
                    "type": "string",
                    "enum": [
                      "off",
                      "no_number",
                      "missing",
                      "pending",
                      "approved",
                      "rejected",
                      "blocked"
                    ],
                    "description": "Worst state across the rail templates (blocked, rejected, missing, pending, approved); `off` when not armed or nothing to provision; `no_number` when armed without a sending number."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "wired": {
                    "type": "boolean",
                    "description": "The rail has its template name set (by an admin or on approval)."
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "missing",
                            "pending",
                            "approved",
                            "rejected",
                            "blocked"
                          ]
                        },
                        "rejection_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "language",
                        "state",
                        "rejection_reason"
                      ]
                    }
                  }
                },
                "required": [
                  "armed",
                  "template_state",
                  "reason",
                  "wired",
                  "templates"
                ]
              },
              "reminder_whatsapp": {
                "type": "object",
                "properties": {
                  "armed": {
                    "type": "boolean",
                    "description": "The rail is switched on (`enabled: true`)."
                  },
                  "template_state": {
                    "type": "string",
                    "enum": [
                      "off",
                      "no_number",
                      "missing",
                      "pending",
                      "approved",
                      "rejected",
                      "blocked"
                    ],
                    "description": "Worst state across the rail templates (blocked, rejected, missing, pending, approved); `off` when not armed or nothing to provision; `no_number` when armed without a sending number."
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "wired": {
                    "type": "boolean",
                    "description": "The rail has its template name set (by an admin or on approval)."
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        },
                        "state": {
                          "type": "string",
                          "enum": [
                            "missing",
                            "pending",
                            "approved",
                            "rejected",
                            "blocked"
                          ]
                        },
                        "rejection_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "language",
                        "state",
                        "rejection_reason"
                      ]
                    }
                  }
                },
                "required": [
                  "armed",
                  "template_state",
                  "reason",
                  "wired",
                  "templates"
                ]
              }
            },
            "required": [
              "staff_whatsapp",
              "unanswered_supervisor_whatsapp",
              "booking_whatsapp",
              "finanzas_whatsapp",
              "reminder_whatsapp"
            ],
            "description": "Read-only: the template state of each staff WhatsApp rail. Ignored on write."
          },
          "name": {
            "type": "string",
            "description": "Workspace display name."
          },
          "timezone": {
            "type": "string",
            "description": "IANA time zone, e.g. `America/Santiago`."
          },
          "language": {
            "type": "string",
            "description": "Default language, e.g. `es`."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency, e.g. `CLP`."
          },
          "date_format": {
            "type": "string",
            "enum": [
              "latam",
              "us",
              "iso"
            ]
          },
          "website": {
            "type": "string",
            "description": "The workspace's declared public website."
          },
          "product_url_template": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the store's own product pages live, e.g. `https://macphones.cl/iphone/{slug}`. Placeholders (the product's fields): `{slug}` (title as a URL slug), `{sku}`, `{id}`, `{brand}`. The agent's stock search returns each product's page link when this is set; a product can override it with `attributes.public_url`. Send `null` or an empty string to clear."
          },
          "ads": {
            "type": "object",
            "properties": {
              "tag_choice": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "site",
                  "no_site"
                ],
                "description": "How the «Instalar el tag» step was answered: `site` (the tag goes on the workspace website) or `no_site` (no website; skipped on purpose). Absent/`null` = never answered."
              },
              "tag_choice_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Server-stamped on every `tag_choice` write."
              },
              "goal": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "outcomes",
                      "value",
                      "roas"
                    ],
                    "description": "`outcomes` = results from ads this month; `value` = attributed value (CLP); `roas` = return (×)."
                  },
                  "target": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "The month’s target. `roas` ≤ 100; `value` ≤ 10^10; `outcomes` ≤ 10^6."
                  },
                  "period": {
                    "type": "string",
                    "enum": [
                      "month"
                    ],
                    "description": "The only period: the calendar month."
                  }
                },
                "required": [
                  "kind",
                  "target",
                  "period"
                ],
                "additionalProperties": false,
                "description": "The monthly goal the Ads hero measures pace against."
              },
              "specialty_map": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "object",
                  "properties": {
                    "match": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120,
                      "description": "A fragment of the campaign, ad-set or ad name (accent- and case-insensitive)."
                    },
                    "especialidad": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120,
                      "description": "The roster specialty it books (e.g. «TTM»)."
                    },
                    "professional_ids": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64
                      },
                      "maxItems": 20,
                      "description": "The vendor professional ids (`clinic_professional.external_id`) it books with."
                    },
                    "visit_minutes": {
                      "type": "integer",
                      "minimum": 5,
                      "maximum": 480,
                      "description": "How long the entry visit (the evaluation) takes."
                    },
                    "evaluation_price": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100000000,
                      "description": "What the entry visit costs (CLP)."
                    }
                  },
                  "required": [
                    "match"
                  ],
                  "additionalProperties": false
                },
                "description": "Which specialty each campaign / ad set / ad books (a name fragment → especialidad, professionals, entry-visit minutes and price). The agenda-capacity check behind «Pausar» / «Subir» reads it first; absent = the name match."
              },
              "goal_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Server-stamped on every `goal` write."
              },
              "goal_by": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Who last wrote the goal: the member's user id, or the API key id."
              },
              "dismissed_actions": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "`{ [action key]: until ISO }` — Ads actions hidden until that instant; expired entries are pruned on every write."
              }
            },
            "description": "Vitrina Ads workspace choices; absent until first answered."
          },
          "voice": {
            "type": "object",
            "properties": {
              "brand_name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[^{}\\r\\n\\t]+$",
                "description": "Brand name templates say literally (\"te escribimos de Automec\"). Absent = the workspace name."
              },
              "register": {
                "type": "string",
                "enum": [
                  "tu",
                  "usted"
                ],
                "description": "How templates address people. Absent = `usted` for healthcare, `tu` otherwise."
              },
              "opening_marks": {
                "type": "string",
                "enum": [
                  "none",
                  "keep"
                ],
                "description": "«Signos de apertura» in the AI's chat replies and follow-ups. `none` (default when absent): no opening «¿» «¡», the way people type in a Chilean chat; removed at send time from every AI message. `keep`: the agent writes them. An agent can override it (`behavior_policies.opening_marks`)."
              },
              "agent_register": {
                "type": "string",
                "enum": [
                  "tu",
                  "usted",
                  "mirror"
                ],
                "description": "«Trato» of the AI agents: `tu`, `usted`, or `mirror` (tú until the customer uses usted). Absent: each agent's own instructions decide. Wins over the agent's instructions; an agent can override it (`behavior_policies.register`)."
              }
            },
            "additionalProperties": false,
            "description": "The stored tenant voice (brand name + tú/usted) customer-facing WhatsApp templates are written in; absent until set."
          },
          "agent_language": {
            "type": "object",
            "properties": {
              "banned_terms": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 2,
                  "maxLength": 60,
                  "pattern": "^[^\\r\\n\\t]+$"
                },
                "maxItems": 100,
                "description": "Words the AI agent must never write to customers (case- and accent-insensitive, whole words), merged with the platform es-CL list. A reply that uses one is rewritten without it before it is sent. Up to 100 terms of 2 to 60 characters."
              }
            },
            "additionalProperties": false,
            "description": "The AI agent's vocabulary: the workspace's banned terms; absent until set."
          },
          "voice_resolved": {
            "type": "object",
            "properties": {
              "brand_name": {
                "type": "string"
              },
              "register": {
                "type": "string",
                "enum": [
                  "tu",
                  "usted"
                ]
              },
              "brand_name_source": {
                "type": "string",
                "enum": [
                  "stored",
                  "workspace_name"
                ]
              },
              "register_source": {
                "type": "string",
                "enum": [
                  "stored",
                  "vertical_default"
                ]
              }
            },
            "required": [
              "brand_name",
              "register",
              "brand_name_source",
              "register_source"
            ],
            "description": "Read-only: the voice templates are actually written in (stored value or default), with where each part came from. Ignored on write."
          },
          "slack_webhook_url": {
            "type": "string",
            "enum": [
              "••••"
            ],
            "description": "Write-only secret: the Slack incoming-webhook URL is never returned. `••••` when one is configured; absent otherwise. Read `slack_webhook_configured` instead."
          },
          "slack_webhook_configured": {
            "type": "boolean",
            "description": "Whether a Slack incoming webhook is configured for workspace alerts."
          },
          "manage_workshop": {
            "type": "boolean",
            "description": "«Gestionar taller en Vitrina» (automotive). On: the workspace has the «Servicio de vehículos» workshop board next to Soporte. Never set → reads true exactly when that board already exists. Writing `true` creates the board (idempotent; refused with 400 outside the automotive vertical); `false` removes it only while no ticket was ever placed on it — a board in use is kept (see `workshop_board.in_use`)."
          },
          "workshop_board": {
            "type": "object",
            "properties": {
              "pipeline_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid",
                "description": "The «Servicio de vehículos» board; null when there is none."
              },
              "in_use": {
                "type": "boolean",
                "description": "A ticket was ever placed on the board; turning `manage_workshop` off keeps it."
              }
            },
            "required": [
              "pipeline_id",
              "in_use"
            ],
            "description": "Read-only: the workshop board `manage_workshop` governs. Ignored on write."
          }
        }
      },
      "TenantSettingsPublicWrite": {
        "type": "object",
        "properties": {
          "trade_in": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "price_table": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "model": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80
                    },
                    "top": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "used": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "min_battery_pct": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "low_battery_factor": {
                      "type": "number",
                      "exclusiveMinimum": 0,
                      "maximum": 1
                    },
                    "direct_purchase": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "model",
                    "top",
                    "used"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1,
                "maxItems": 500
              },
              "not_bought": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 80
                },
                "maxItems": 200
              },
              "floor_factor": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1
              },
              "perfect_floor_factor": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 1
              },
              "rounding": {
                "type": "integer",
                "minimum": 1,
                "maximum": 1000000
              },
              "perfect_battery_pct": {
                "type": "integer",
                "minimum": 1,
                "maximum": 100
              },
              "currency": {
                "type": "string",
                "pattern": "^[A-Z]{3}$"
              }
            },
            "required": [
              "price_table"
            ],
            "additionalProperties": false,
            "description": "Trade-in buy table («parte de pago»), for a business that buys used devices: `price_table` rows `{model, top, used, min_battery_pct?, low_battery_factor?, direct_purchase?}` (`top` = max paid for a flawless unit, `used` = with normal wear, whole currency units; capacity does not change the value; `min_battery_pct` = a known battery under it is not bought; `low_battery_factor` (0–1) = a known battery under `perfect_battery_pct` caps the price at `used × low_battery_factor` instead of `used`; `direct_purchase: false` = the model is taken only as part of the payment for another item, never bought outright — absent = true), `not_bought` (models never bought), `floor_factor` (0–1, default 0.7: the low end for a unit with issues or unknown condition is `used × floor_factor`), `perfect_floor_factor` (0–1, default 0.85: the low end for a flawless unit is `used × perfect_floor_factor`, its high end is `top`), `rounding` (default 10000), `perfect_battery_pct` (default 90), `currency` (default CLP). On an ecommerce workspace its presence offers the agents the `estimate_trade_in` tool (a wide range of what the customer would still pay) and the `quote_buyback` tool (the most paid for one device sold outright, or the discount for a `direct_purchase: false` model); the business’s own site reads it at `GET /storefront/trade-in`. Replaced whole on write; `null` clears it."
          },
          "ads": {
            "type": "object",
            "properties": {
              "tag_choice": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "site",
                  "no_site"
                ]
              },
              "goal": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "outcomes",
                      "value",
                      "roas"
                    ],
                    "description": "`outcomes` = results from ads this month; `value` = attributed value (CLP); `roas` = return (×)."
                  },
                  "target": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "The month’s target. `roas` ≤ 100; `value` ≤ 10^10; `outcomes` ≤ 10^6."
                  },
                  "period": {
                    "type": "string",
                    "enum": [
                      "month"
                    ],
                    "description": "The only period: the calendar month."
                  }
                },
                "required": [
                  "kind",
                  "target",
                  "period"
                ],
                "additionalProperties": false
              },
              "dismissed_actions": {
                "type": "object",
                "additionalProperties": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                }
              },
              "specialty_map": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "type": "object",
                  "properties": {
                    "match": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120,
                      "description": "A fragment of the campaign, ad-set or ad name (accent- and case-insensitive)."
                    },
                    "especialidad": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120,
                      "description": "The roster specialty it books (e.g. «TTM»)."
                    },
                    "professional_ids": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64
                      },
                      "maxItems": 20,
                      "description": "The vendor professional ids (`clinic_professional.external_id`) it books with."
                    },
                    "visit_minutes": {
                      "type": "integer",
                      "minimum": 5,
                      "maximum": 480,
                      "description": "How long the entry visit (the evaluation) takes."
                    },
                    "evaluation_price": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 100000000,
                      "description": "What the entry visit costs (CLP)."
                    }
                  },
                  "required": [
                    "match"
                  ],
                  "additionalProperties": false
                },
                "maxItems": 50
              }
            },
            "additionalProperties": false,
            "description": "Vitrina Ads workspace choices. `tag_choice`: how the «Instalar el tag» step was answered — `site` (the tag goes on the workspace's website, pasted or through Google Tag Manager) or `no_site` (no website; step skipped on purpose); `null` clears it. `goal`: the monthly goal the Ads hero measures pace against (`{kind, target, period:'month'}`; `null` clears it — `PUT /ads/goal` is the dedicated door). `dismissed_actions`: `{ [action id]: until ISO }` merged onto the stored map (`null` removes an id; expired entries are pruned). `specialty_map`: which specialty (and professionals, entry-visit length and price) each campaign / ad set / ad books — the agenda-capacity check behind «Pausar» / «Subir» reads it before the name match; replaces the stored list whole (`null` clears it). Merged into the stored `ads` object; the server stamps `tag_choice_at` / `goal_at` / `goal_by` on every write, and `GET /tenant/settings` returns them under `ads`."
          },
          "voice": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand_name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[^{}\\r\\n\\t]+$",
                "description": "Brand name templates say literally (\"te escribimos de Automec\"). Absent = the workspace name."
              },
              "register": {
                "type": "string",
                "enum": [
                  "tu",
                  "usted"
                ],
                "description": "How templates address people. Absent = `usted` for healthcare, `tu` otherwise."
              },
              "opening_marks": {
                "type": "string",
                "enum": [
                  "none",
                  "keep"
                ],
                "description": "«Signos de apertura» in the AI's chat replies and follow-ups. `none` (default when absent): no opening «¿» «¡», the way people type in a Chilean chat; removed at send time from every AI message. `keep`: the agent writes them. An agent can override it (`behavior_policies.opening_marks`)."
              },
              "agent_register": {
                "type": "string",
                "enum": [
                  "tu",
                  "usted",
                  "mirror"
                ],
                "description": "«Trato» of the AI agents: `tu`, `usted`, or `mirror` (tú until the customer uses usted). Absent: each agent's own instructions decide. Wins over the agent's instructions; an agent can override it (`behavior_policies.register`)."
              }
            },
            "additionalProperties": false
          },
          "agent_language": {
            "type": "object",
            "properties": {
              "banned_terms": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 2,
                  "maxLength": 60,
                  "pattern": "^[^\\r\\n\\t]+$"
                },
                "maxItems": 100,
                "description": "Words the AI agent must never write to customers (case- and accent-insensitive, whole words), merged with the platform es-CL list. A reply that uses one is rewritten without it before it is sent. Up to 100 terms of 2 to 60 characters."
              }
            },
            "additionalProperties": false
          },
          "notifications": {
            "type": "object",
            "properties": {
              "staff_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  }
                }
              },
              "unanswered_supervisor_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 10
                  },
                  "delay_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 1440,
                    "description": "Minutes of open time a conversation waits on the team before the supervisors get the WhatsApp, once per episode. Unset = 60."
                  }
                }
              },
              "reminder_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  }
                },
                "description": "Staff WhatsApp on every «sin respuesta» reminder, to the assignee (template aviso_sigue_esperando_v1). Opt-in: each send costs a WhatsApp conversation."
              },
              "booking_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "change_template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  }
                }
              },
              "visit_watchers_whatsapp": {
                "type": "object",
                "properties": {
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 5
                  }
                },
                "required": [
                  "user_ids"
                ]
              },
              "finanzas_whatsapp": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "template_language": {
                    "type": "string",
                    "maxLength": 16,
                    "pattern": "^[A-Za-z]{2,3}(?:[_-][A-Za-z0-9]{2,8})?$"
                  },
                  "digest_template_name": {
                    "type": "string",
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$",
                    "description": "The weekly Finanzas digest template (resumen_finanzas_semanal_v1), wired on approval."
                  }
                }
              },
              "stock_alert": {
                "type": "object",
                "properties": {
                  "stage_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 20,
                    "description": "Lead stages that trigger the «Stock faltante» check when a lead moves into them. Empty turns it off."
                  }
                },
                "required": [
                  "stage_ids"
                ]
              }
            }
          },
          "manage_workshop": {
            "type": "boolean",
            "description": "Turn the «Servicio de vehículos» workshop board on (creates it; automotive only) or off (removes it only while it never held a ticket)."
          }
        }
      },
      "LiveUsage": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string"
          },
          "period": {
            "type": "object",
            "properties": {
              "month": {
                "type": "string",
                "description": "`YYYY-MM`, America/Santiago."
              },
              "start": {
                "type": "string"
              },
              "end": {
                "type": "string"
              },
              "elapsed_days": {
                "type": "integer"
              },
              "total_days": {
                "type": "integer"
              },
              "closed": {
                "type": "boolean",
                "description": "The Santiago month has ended (a past `period`)."
              }
            },
            "required": [
              "month",
              "start",
              "end",
              "elapsed_days",
              "total_days",
              "closed"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "metered",
              "fixed_contract",
              "not_billed",
              "complimentary",
              "billing_not_started",
              "external_month",
              "clinic_plan"
            ],
            "description": "`metered`: rate card. `fixed_contract`: one fixed monthly price, usage shown at $0. `not_billed`: usage only, no prices. `complimentary`: never billed («se calcula, no se cobra») but priced exactly like `metered` for reference — lines, totals, projection and UF, `show_prices` true, `next_charge` null; show the amounts with a note that the workspace is not charged. `billing_not_started`: billing begins on `billing_starts_on`. `external_month`: the month is invoiced outside the system. `clinic_plan`: billed on the plan anniversary, not by this view."
          },
          "billing_starts_on": {
            "type": [
              "string",
              "null"
            ]
          },
          "show_prices": {
            "type": "boolean",
            "description": "False: render usage only, no amounts anywhere. True for `complimentary` too: show the amounts, but as a reference (never charged)."
          },
          "uf": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "clp": {
                "type": "number"
              },
              "date": {
                "type": "string"
              },
              "stale": {
                "type": "boolean"
              }
            },
            "required": [
              "clp",
              "date",
              "stale"
            ],
            "description": "Today's UF, for the CLP estimate only (the close freezes its own)."
          },
          "usage": {
            "type": "object",
            "properties": {
              "conversations": {
                "type": "integer"
              },
              "voice_minutes": {
                "type": "integer"
              },
              "forwarded_voice_minutes": {
                "type": "integer"
              },
              "eval_runs": {
                "type": "integer",
                "description": "Completed agent-eval runs billed this month (queued after an admin accepted the per-run price)."
              },
              "email_marketable_contacts": {
                "type": "integer"
              },
              "email_marketing_sent": {
                "type": "integer",
                "description": "Email-campaign messages sent this month; the contact-base line is billed only when above 0."
              }
            },
            "required": [
              "conversations",
              "voice_minutes",
              "forwarded_voice_minutes",
              "eval_runs",
              "email_marketable_contacts",
              "email_marketing_sent"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "conversations",
                    "email_contacts",
                    "voice_minutes",
                    "forwarded_voice_minutes",
                    "eval_runs",
                    "addon_flat",
                    "quota_overage",
                    "contract_fixed",
                    "conversation_credit",
                    "manual_credit"
                  ]
                },
                "count": {
                  "type": "number"
                },
                "uf_unit": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "uf_subtotal": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "clp_amount": {
                  "type": "integer"
                },
                "included_in_contract": {
                  "type": "boolean"
                },
                "feature": {
                  "type": "string"
                },
                "active_fraction": {
                  "type": "number"
                },
                "complimentary": {
                  "type": "boolean",
                  "description": "An add-on granted free: shown at 0 («Incluido sin costo»)."
                },
                "clp_month": {
                  "type": "integer",
                  "description": "A peso-priced flat add-on (`flatClpMonth`): its monthly list price in whole CLP, neto. Display only; the line bills `uf_subtotal`."
                }
              },
              "required": [
                "kind",
                "count",
                "uf_unit",
                "uf_subtotal"
              ]
            }
          },
          "totals": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "subtotal_uf": {
                "type": "number"
              },
              "clp_net": {
                "type": "integer"
              },
              "iva_amount": {
                "type": "integer"
              },
              "clp_total": {
                "type": "integer"
              }
            },
            "required": [
              "subtotal_uf",
              "clp_net",
              "iva_amount",
              "clp_total"
            ]
          },
          "projection": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "linear_elapsed_days"
                ]
              },
              "elapsed_days": {
                "type": "integer"
              },
              "total_days": {
                "type": "integer"
              },
              "conversations": {
                "type": "integer"
              },
              "voice_minutes": {
                "type": "integer"
              },
              "forwarded_voice_minutes": {
                "type": "integer"
              },
              "eval_runs": {
                "type": "integer"
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "conversations",
                        "email_contacts",
                        "voice_minutes",
                        "forwarded_voice_minutes",
                        "eval_runs",
                        "addon_flat",
                        "quota_overage",
                        "contract_fixed",
                        "conversation_credit",
                        "manual_credit"
                      ]
                    },
                    "count": {
                      "type": "number"
                    },
                    "uf_unit": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "uf_subtotal": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "clp_amount": {
                      "type": "integer"
                    },
                    "included_in_contract": {
                      "type": "boolean"
                    },
                    "feature": {
                      "type": "string"
                    },
                    "active_fraction": {
                      "type": "number"
                    },
                    "complimentary": {
                      "type": "boolean",
                      "description": "An add-on granted free: shown at 0 («Incluido sin costo»)."
                    },
                    "clp_month": {
                      "type": "integer",
                      "description": "A peso-priced flat add-on (`flatClpMonth`): its monthly list price in whole CLP, neto. Display only; the line bills `uf_subtotal`."
                    }
                  },
                  "required": [
                    "kind",
                    "count",
                    "uf_unit",
                    "uf_subtotal"
                  ]
                }
              },
              "totals": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "subtotal_uf": {
                    "type": "number"
                  },
                  "clp_net": {
                    "type": "integer"
                  },
                  "iva_amount": {
                    "type": "integer"
                  },
                  "clp_total": {
                    "type": "integer"
                  }
                },
                "required": [
                  "subtotal_uf",
                  "clp_net",
                  "iva_amount",
                  "clp_total"
                ]
              }
            },
            "required": [
              "basis",
              "elapsed_days",
              "total_days",
              "conversations",
              "voice_minutes",
              "forwarded_voice_minutes",
              "eval_runs",
              "lines",
              "totals"
            ],
            "description": "An ESTIMATE: month-to-date usage scaled linearly by the share of the Santiago month elapsed. `null` when prices are hidden."
          },
          "next_charge": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "period_label": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "«1 – 31 oct 2026»."
              },
              "cutoff_date": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Last Santiago day the cobro covers (`YYYY-MM-DD`)."
              },
              "issue_date": {
                "type": "string",
                "description": "Santiago day the close issues the cobro (the 1st)."
              },
              "charge_date": {
                "type": "string",
                "description": "Santiago day it is charged (`collection: automatic`) or falls due for a transfer — the `due_at` the close would stamp today."
              },
              "charge_date_label": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "«1 nov 2026»."
              },
              "collection": {
                "type": "string",
                "enum": [
                  "automatic",
                  "transfer"
                ],
                "description": "`automatic` while a payment method is active; otherwise the cobro is paid by transfer within a 3-business-day term."
              },
              "method_state": {
                "type": "string",
                "enum": [
                  "active",
                  "pending",
                  "failed",
                  "revoked",
                  "none"
                ],
                "description": "`pending`: enrolment not confirmed yet (paid by transfer until it is)."
              },
              "method": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "provider": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "bank_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "account_last4": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "card_brand": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "card_last4": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "provider",
                  "status"
                ]
              },
              "method_label": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "«tu cuenta corriente BancoEstado ••1234», as the cobro names it; only while active."
              },
              "transfer_available": {
                "type": "boolean",
                "description": "True when Vitrina's transfer account is configured."
              },
              "uf_value": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Today's UF (an estimate: the close freezes its own day's UF)."
              },
              "uf_value_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "so_far": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "detail": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "quantity": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "uf_unit": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "uf_subtotal": {
                          "type": "number"
                        },
                        "clp_unit": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "Unit price in pesos at the invoice’s UF value, to the centavo (`uf_unit × uf_value`) — display only; the line’s pesos are `clp_subtotal`."
                        },
                        "clp_amount": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "clp_subtotal": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The line's net pesos as the cobro prints it; the lines add up exactly to `clp_net`. 0 for an included line; null without a UF value."
                        },
                        "included": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "contract",
                            "complimentary"
                          ]
                        },
                        "conversation_ref": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the credited conversation (`C-1042`)."
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: why the conversation was exempted."
                        },
                        "source_invoice_number": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the invoice that billed it originally."
                        },
                        "source_dte_folio": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the folio of the factura that invoice already had, when it had one."
                        }
                      },
                      "required": [
                        "kind",
                        "label",
                        "detail",
                        "quantity",
                        "uf_unit",
                        "uf_subtotal",
                        "clp_amount",
                        "included"
                      ]
                    }
                  },
                  "uf_subtotal": {
                    "type": "number"
                  },
                  "clp_net": {
                    "type": "integer"
                  },
                  "iva_amount": {
                    "type": "integer",
                    "description": "round(clp_net × 0.19), the rounding the close uses."
                  },
                  "clp_total": {
                    "type": "integer"
                  }
                },
                "required": [
                  "lines",
                  "uf_subtotal",
                  "clp_net",
                  "iva_amount",
                  "clp_total"
                ],
                "description": "The month so far, priced as the close would price it now."
              },
              "estimate": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "detail": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "quantity": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "uf_unit": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "uf_subtotal": {
                          "type": "number"
                        },
                        "clp_unit": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "Unit price in pesos at the invoice’s UF value, to the centavo (`uf_unit × uf_value`) — display only; the line’s pesos are `clp_subtotal`."
                        },
                        "clp_amount": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "clp_subtotal": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "The line's net pesos as the cobro prints it; the lines add up exactly to `clp_net`. 0 for an included line; null without a UF value."
                        },
                        "included": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "contract",
                            "complimentary"
                          ]
                        },
                        "conversation_ref": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the credited conversation (`C-1042`)."
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: why the conversation was exempted."
                        },
                        "source_invoice_number": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the invoice that billed it originally."
                        },
                        "source_dte_folio": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Credit lines: the folio of the factura that invoice already had, when it had one."
                        }
                      },
                      "required": [
                        "kind",
                        "label",
                        "detail",
                        "quantity",
                        "uf_unit",
                        "uf_subtotal",
                        "clp_amount",
                        "included"
                      ]
                    }
                  },
                  "uf_subtotal": {
                    "type": "number"
                  },
                  "clp_net": {
                    "type": "integer"
                  },
                  "iva_amount": {
                    "type": "integer",
                    "description": "round(clp_net × 0.19), the rounding the close uses."
                  },
                  "clp_total": {
                    "type": "integer"
                  }
                },
                "required": [
                  "lines",
                  "uf_subtotal",
                  "clp_net",
                  "iva_amount",
                  "clp_total"
                ],
                "description": "The month-end estimate (linear by elapsed days): what is likely charged."
              }
            },
            "required": [
              "period_label",
              "cutoff_date",
              "issue_date",
              "charge_date",
              "charge_date_label",
              "collection",
              "method_state",
              "method",
              "method_label",
              "transfer_available",
              "uf_value",
              "uf_value_date",
              "so_far",
              "estimate"
            ],
            "description": "The cobro the close of this month will issue: dates, how it is collected, and its lines in pesos so far and at month end, built like the hosted cobro. `null` when prices are hidden."
          },
          "addons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "feature": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "active",
                    "canceled",
                    "suspended"
                  ]
                },
                "included_reason": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "complimentary",
                    "contract",
                    "not_billed",
                    "partner",
                    "waiver"
                  ]
                },
                "waiver_until": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "feature",
                "status",
                "included_reason",
                "waiver_until"
              ]
            }
          },
          "amount_source": {
            "type": "string",
            "enum": [
              "estimate",
              "invoice",
              "external",
              "none"
            ],
            "description": "Where `lines`/`totals` come from. `estimate`: the open month priced at today's UF, or a closed `complimentary` month priced on the rate card at its close day's UF (reference only). `invoice`: the issued cobro, exactly as frozen at the close (never recomputed); a cobro issued by hand for the month reads the same, with its totals. `external`: the month was invoiced by hand outside the system and its amount is not on this month (no cobro on record, or it was billed on a cobro listed on another month — then `invoice` names that cobro). `none`: nothing was billed or prices are hidden — usage only."
          },
          "invoice": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "issued_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "number",
              "status",
              "issued_at"
            ],
            "description": "A closed month's issued cobro — for a hand-billed month billed on another month's cobro, that cobro; `null` otherwise."
          },
          "conversations_source": {
            "type": "string",
            "enum": [
              "invoice",
              "live"
            ],
            "description": "`invoice`: conversations counted from the set the cobro froze at the close (reconciles with it exactly); `live`: from the meter."
          },
          "daily": {
            "type": "object",
            "properties": {
              "days": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "`YYYY-MM-DD` (America/Santiago): every day of a closed month; day 1 to today for the open month."
              },
              "series": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "metric": {
                      "type": "string",
                      "enum": [
                        "conversations",
                        "voice_minutes",
                        "forwarded_voice_minutes",
                        "eval_runs",
                        "email_marketing_sent",
                        "addon_usage"
                      ]
                    },
                    "feature": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "`addon_usage` only: the add-on feature key."
                    },
                    "total": {
                      "type": "integer",
                      "description": "Σ `values`."
                    },
                    "values": {
                      "type": "array",
                      "items": {
                        "type": "integer"
                      },
                      "description": "One value per entry of `days`."
                    }
                  },
                  "required": [
                    "metric",
                    "feature",
                    "total",
                    "values"
                  ]
                }
              }
            },
            "required": [
              "days",
              "series"
            ],
            "description": "The month’s meters per day. Counts only, never content."
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "`YYYY-MM`."
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "invoice",
                    "external"
                  ]
                },
                "invoice_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "issued_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "uf_subtotal": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "clp_net": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "iva_amount": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "clp_total": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "lines": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "conversations",
                          "email_contacts",
                          "voice_minutes",
                          "forwarded_voice_minutes",
                          "eval_runs",
                          "addon_flat",
                          "quota_overage",
                          "contract_fixed",
                          "conversation_credit",
                          "manual_credit"
                        ]
                      },
                      "count": {
                        "type": "number"
                      },
                      "uf_unit": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "uf_subtotal": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "clp_amount": {
                        "type": "integer"
                      },
                      "included_in_contract": {
                        "type": "boolean"
                      },
                      "feature": {
                        "type": "string"
                      },
                      "active_fraction": {
                        "type": "number"
                      },
                      "complimentary": {
                        "type": "boolean",
                        "description": "An add-on granted free: shown at 0 («Incluido sin costo»)."
                      },
                      "clp_month": {
                        "type": "integer",
                        "description": "A peso-priced flat add-on (`flatClpMonth`): its monthly list price in whole CLP, neto. Display only; the line bills `uf_subtotal`."
                      }
                    },
                    "required": [
                      "kind",
                      "count",
                      "uf_unit",
                      "uf_subtotal"
                    ]
                  }
                }
              },
              "required": [
                "month",
                "source",
                "invoice_id",
                "number",
                "status",
                "issued_at",
                "uf_subtotal",
                "clp_net",
                "iva_amount",
                "clp_total",
                "lines"
              ]
            }
          }
        },
        "required": [
          "as_of",
          "period",
          "mode",
          "billing_starts_on",
          "show_prices",
          "uf",
          "usage",
          "lines",
          "totals",
          "projection",
          "next_charge",
          "addons",
          "amount_source",
          "invoice",
          "conversations_source",
          "daily",
          "history"
        ]
      },
      "ConversationDispute": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "conversation_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "approved",
              "rejected"
            ]
          },
          "reason_code": {
            "type": "string",
            "enum": [
              "not_real_customer",
              "duplicate",
              "answered_by_human",
              "other"
            ]
          },
          "reason_label": {
            "type": "string"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ]
          },
          "resolution": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "credit",
              "refund"
            ],
            "description": "Approved only: `credit` = discounted on the next invoice, `refund` = paid back."
          },
          "resolution_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rejected only: why it was rejected."
          },
          "created_at": {
            "type": "string"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "conversation_id",
          "status",
          "reason_code",
          "reason_label",
          "comment",
          "resolution",
          "resolution_note",
          "created_at",
          "resolved_at"
        ],
        "description": "The latest «Reportar» of this conversation, if any (#3610)."
      },
      "BilledConversationsPage": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "month": {
                "type": "string"
              },
              "start": {
                "type": "string"
              },
              "end": {
                "type": "string"
              },
              "closed": {
                "type": "boolean"
              },
              "source": {
                "type": "string",
                "enum": [
                  "invoice",
                  "live"
                ],
                "description": "`invoice` = the set the invoice froze at the close; `live` = the meter now."
              }
            },
            "required": [
              "month",
              "start",
              "end",
              "closed",
              "source"
            ]
          },
          "invoice": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "number": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "issued_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "number",
              "status",
              "issued_at"
            ]
          },
          "summary": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer",
                "minimum": 0
              },
              "unreviewed": {
                "type": "integer",
                "minimum": 0
              },
              "exempted_after_close": {
                "type": "integer",
                "minimum": 0
              },
              "conversations_uf": {
                "type": "number",
                "minimum": 0
              }
            },
            "required": [
              "count",
              "unreviewed",
              "exempted_after_close",
              "conversations_uf"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "conversation_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Human id, e.g. `C-3052`."
                },
                "first_billable_at": {
                  "type": "string",
                  "description": "The billable moment: the first agent reply that reached a customer who wrote."
                },
                "channel": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "contact_last4": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Last 4 digits of the contact’s phone; the full number is never returned."
                },
                "review_state": {
                  "type": "string",
                  "enum": [
                    "billable",
                    "unreviewed"
                  ],
                  "description": "Review verdict at the close (or now, for the open month). `unreviewed` bills (policy)."
                },
                "counted_because": {
                  "type": "string",
                  "enum": [
                    "customer_wrote_agent_replied"
                  ]
                },
                "dispute": {
                  "$ref": "#/components/schemas/ConversationDispute"
                },
                "exempted_after_close": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "reason": {
                      "type": "string"
                    },
                    "exempted_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "credited_on_invoice": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "reason",
                    "exempted_at",
                    "credited_on_invoice"
                  ],
                  "description": "Set when a review exempted it after its invoice was issued; it is then credited on a later invoice."
                }
              },
              "required": [
                "conversation_id",
                "display_id",
                "first_billable_at",
                "channel",
                "contact_last4",
                "review_state",
                "counted_because",
                "exempted_after_close"
              ]
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "period",
          "invoice",
          "summary",
          "items",
          "next_cursor"
        ]
      },
      "ClaimComprobante": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "invoice_id": {
            "type": "string",
            "format": "uuid",
            "description": "The Cobro the claim is on."
          },
          "claimed_at": {
            "type": "string",
            "description": "ISO instant of the «Ya pagué» this file belongs to."
          },
          "filename": {
            "type": "string",
            "description": "Display name (sanitised: spaces and accents become `_`)."
          },
          "mime": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/jpeg",
              "image/png"
            ]
          },
          "size_bytes": {
            "type": "integer"
          },
          "uploaded_at": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "confirmed",
              "rejected",
              "closed"
            ],
            "description": "`pending`: the claim waits for Vitrina (the file can still be replaced). `confirmed`: Vitrina confirmed the payment; the file stays with it. `rejected`: Vitrina did not find the transfer; kept as history. `closed`: the cobro was paid another way while the claim waited."
          },
          "rejected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "received_payment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The «Pago recibido» a confirmed claim became (it carries this file)."
          }
        },
        "required": [
          "id",
          "invoice_id",
          "claimed_at",
          "filename",
          "mime",
          "size_bytes",
          "uploaded_at",
          "state",
          "rejected_at",
          "received_payment_id"
        ],
        "description": "#7094: the file attached to this claim («comprobante adjunto»), or null."
      },
      "BillingHistoryRow": {
        "type": "object",
        "properties": {
          "row_key": {
            "type": "string",
            "description": "Stable, unique row key (list key / cursor anchor): the invoice id, or `manual:YYYY-MM`. Not a resource id — use `invoice_id`."
          },
          "kind": {
            "type": "string",
            "enum": [
              "invoice",
              "manual"
            ],
            "description": "`invoice` = a cobro issued by Vitrina; `manual` = a month invoiced outside the system."
          },
          "period": {
            "type": "string",
            "description": "Billing month, `YYYY-MM` (America/Santiago)."
          },
          "invoice_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The cobro number. Null for a manual month without one."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "overdue",
              "paid",
              "void",
              "billed_outside"
            ],
            "description": "`billed_outside` = a month invoiced by hand with no cobro on record («Facturado por correo»). A hand-billed month whose cobro is on record reads that cobro’s status."
          },
          "clp_net": {
            "type": [
              "integer",
              "null"
            ]
          },
          "iva_amount": {
            "type": [
              "integer",
              "null"
            ]
          },
          "clp_total": {
            "type": [
              "integer",
              "null"
            ]
          },
          "issued_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Santiago `YYYY-MM-DD`."
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_date": {
            "type": [
              "string",
              "null"
            ],
            "description": "Santiago `YYYY-MM-DD` of `paid_at`."
          },
          "conversation_count": {
            "type": "integer",
            "description": "Conversations of the month: the set the cobro froze at the close, else the live meter — the same count `GET /billing/conversations?period=` reports."
          },
          "payable": {
            "type": "boolean",
            "description": "True when «Pagar» applies: an unpaid cobro whose charge is not in flight (`POST /billing/invoices/{id}/pay`)."
          },
          "billed_in": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "invoice_id": {
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "period": {
                "type": "string",
                "description": "`YYYY-MM` of the row that carries the cobro."
              }
            },
            "required": [
              "invoice_id",
              "number",
              "period"
            ],
            "description": "A month invoiced by hand that was billed on a cobro listed on another month (one cobro for two months): that cobro. The amount is on the cobro’s own row, never repeated here. Null otherwise."
          },
          "concept": {
            "type": [
              "string",
              "null"
            ],
            "description": "What a cobro billed only with one-time charges was for (their labels, e.g. «Implementación de Vitrina»), so the row does not read as a usage month. Null for a usage month or a month invoiced by hand."
          },
          "documents": {
            "type": "object",
            "properties": {
              "cobro_pdf_path": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "API path of the cobro PDF (`GET /billing/invoices/{id}/pdf`)."
              },
              "factura_state": {
                "type": "string",
                "enum": [
                  "issued",
                  "after_payment",
                  "processing",
                  "none"
                ],
                "description": "`issued` (folio, PDF and XML available), `after_payment` (unpaid: the factura is emitted once paid), `processing` (paid, emission in flight), `none` (void or manual month)."
              },
              "factura": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "factura",
                      "boleta"
                    ],
                    "description": "Which tax document this month carries."
                  },
                  "folio": {
                    "type": "string"
                  },
                  "pdf_path": {
                    "type": "string"
                  },
                  "xml_path": {
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "folio",
                  "pdf_path",
                  "xml_path"
                ]
              }
            },
            "required": [
              "cobro_pdf_path",
              "factura_state",
              "factura"
            ]
          }
        },
        "required": [
          "row_key",
          "kind",
          "period",
          "invoice_id",
          "number",
          "status",
          "clp_net",
          "iva_amount",
          "clp_total",
          "issued_at",
          "due_date",
          "paid_at",
          "paid_date",
          "conversation_count",
          "payable",
          "billed_in",
          "concept",
          "documents"
        ]
      },
      "BillingHistoryPage": {
        "type": "object",
        "properties": {
          "filter": {
            "type": "string",
            "enum": [
              "all",
              "paid",
              "open"
            ],
            "description": "`all` (default), `paid` (Pagadas) or `open` (Pendientes: por pagar or vencido)."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BillingHistoryRow"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "filter",
          "items",
          "next_cursor"
        ]
      },
      "BillingProfile": {
        "type": "object",
        "properties": {
          "legal_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "rut": {
            "type": [
              "string",
              "null"
            ]
          },
          "giro": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": "object",
            "properties": {
              "line1": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "line2": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "city": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "region": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "postal_code": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "country": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comuna_code": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "line1",
              "line2",
              "city",
              "region",
              "postal_code",
              "country",
              "comuna_code"
            ]
          },
          "billing_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "cc_emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "billing_emails": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every address that receives the billing emails, in order: `billing_email` first, then `cc_emails`. All are recipients (to), none is a mere copy."
          },
          "complete": {
            "type": "boolean",
            "description": "True when a factura electrónica can be issued with this data."
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "rut",
                "legal_name",
                "giro",
                "address_street",
                "comuna",
                "billing_email"
              ]
            },
            "description": "Required fields still missing; empty when `complete`."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "in_app",
              "import",
              "migrated"
            ],
            "description": "in_app = saved in the app; import = Tally/Contratos import; migrated = copied from older records."
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the client confirmed the data: saved in the app (source in_app), or typed into the onboarding form (source import, the import time). Null = migrated data nobody has reviewed. Whether a factura can be issued is `complete`, not this."
          }
        },
        "required": [
          "legal_name",
          "rut",
          "giro",
          "address",
          "billing_email",
          "cc_emails",
          "billing_emails",
          "complete",
          "missing",
          "source",
          "confirmed_at"
        ]
      },
      "PaymentMethodSetup": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "fintoc",
              "mercadopago",
              "transbank_oneclick"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "active"
            ]
          },
          "mandateId": {
            "type": "string"
          },
          "setupUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "setupToken": {
            "type": [
              "string",
              "null"
            ],
            "description": "Transbank Oneclick only: send the browser to `setupUrl` with a form POST carrying this value as the `TBK_TOKEN` field."
          }
        },
        "required": [
          "provider",
          "status",
          "mandateId",
          "setupUrl"
        ]
      },
      "PaymentMethodStatus": {
        "type": "object",
        "properties": {
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "fintoc",
              "mercadopago",
              "transbank_oneclick"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "pending",
              "active",
              "revoked",
              "failed"
            ]
          },
          "mandateId": {
            "type": [
              "string",
              "null"
            ]
          },
          "card": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last4": {
                "type": "string"
              }
            },
            "required": [
              "brand",
              "last4"
            ],
            "description": "The saved card of the active mandate (Transbank Oneclick): brand and last four digits only."
          },
          "bankAccount": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "bankName": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last4": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "bankName",
              "last4"
            ],
            "description": "The debited account of the active PAC mandate (Fintoc): bank name and last digits only."
          },
          "pacBanks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name"
              ]
            },
            "description": "Banks where a PAC (automatic debit from a current account) can be enrolled through Fintoc. A client whose bank is not listed pays by card (when offered) or by transfer."
          },
          "availableProviders": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "fintoc",
                "mercadopago",
                "transbank_oneclick"
              ]
            },
            "description": "Providers that can be enrolled right now. `transbank_oneclick` appears only once Vitrina has its card contract configured."
          }
        },
        "required": [
          "provider",
          "status",
          "mandateId"
        ]
      },
      "PaymentMethodItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "rail": {
            "type": "string",
            "enum": [
              "fintoc_pac",
              "fintoc_card",
              "oneclick",
              "mercadopago"
            ],
            "description": "The rail that holds the method: `fintoc_pac` (automatic debit from a current account through Fintoc), `fintoc_card` (credit or debit card on file at Fintoc), `oneclick` (card on file at Transbank Webpay Oneclick), `mercadopago` (card on file at Mercado Pago)."
          },
          "rail_name": {
            "type": "string",
            "description": "The rail by its own name, ready to print («Tarjeta (Webpay Oneclick)»)."
          },
          "status": {
            "type": "string",
            "enum": [
              "activo",
              "pendiente_firma",
              "rechazado"
            ],
            "description": "`activo` = billable. A PAC can also be `pendiente_firma` (the owner has not signed the bank mandate yet) or `rechazado` (the bank rejected it)."
          },
          "is_default": {
            "type": "boolean",
            "description": "The method the cobros are charged to («Predeterminado»)."
          },
          "bank": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last4": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name",
              "last4"
            ],
            "description": "PAC only: the debited bank and the account’s last digits."
          },
          "card": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "brand": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last4": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "exp_month": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "exp_year": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "kind": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "credit",
                  "debit",
                  "prepaid"
                ],
                "description": "Credit, debit or prepaid, when the rail reports it (Fintoc)."
              }
            },
            "required": [
              "brand",
              "last4",
              "exp_month",
              "exp_year"
            ],
            "description": "Cards only: brand, last four digits, expiry and kind. Transbank Oneclick does not report the expiry, so it is null there."
          },
          "removable": {
            "type": "boolean",
            "description": "False for the default method while a cobro is pending, and while a charge is in progress on the method."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "rail",
          "rail_name",
          "status",
          "is_default",
          "bank",
          "card",
          "removable",
          "created_at"
        ]
      },
      "PaymentMethodsView": {
        "type": "object",
        "properties": {
          "methods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PaymentMethodItem"
            },
            "description": "The default first, then newest first. Removed methods are not listed."
          },
          "default_method_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "pays_by": {
            "type": "string",
            "enum": [
              "method",
              "transfer"
            ],
            "description": "`transfer` when there is no active method: the cobros are paid by bank transfer."
          },
          "pending_cobro": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "invoice_id": {
                "type": "string",
                "format": "uuid"
              },
              "number": {
                "type": "string"
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The reference to write in a transfer comment («VT-7K2QM9»)."
              },
              "due_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "invoice_id",
              "number",
              "reference",
              "due_at"
            ],
            "description": "The oldest cobro still owed (issued, being charged or past due), or null."
          },
          "transfer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "account": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "bank": {
                    "type": "string"
                  },
                  "account_type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "account_number": {
                    "type": "string"
                  },
                  "holder_name": {
                    "type": "string"
                  },
                  "holder_rut": {
                    "type": "string"
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "bank",
                  "account_type",
                  "account_number",
                  "holder_name",
                  "holder_rut",
                  "email"
                ],
                "description": "Vitrina’s account; null while it is not configured."
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The reference of the cobro to pay; null when nothing is owed (each cobro carries its own)."
              },
              "invoice_number": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "account",
              "reference",
              "invoice_number"
            ],
            "description": "Set only when `pays_by` is `transfer`."
          },
          "available_rails": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rail": {
                  "type": "string",
                  "enum": [
                    "fintoc_pac",
                    "fintoc_card",
                    "oneclick",
                    "mercadopago"
                  ],
                  "description": "The rail that holds the method: `fintoc_pac` (automatic debit from a current account through Fintoc), `fintoc_card` (credit or debit card on file at Fintoc), `oneclick` (card on file at Transbank Webpay Oneclick), `mercadopago` (card on file at Mercado Pago)."
                },
                "rail_name": {
                  "type": "string"
                },
                "recommended": {
                  "type": "boolean"
                },
                "warning": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Text to show on the setup screen before continuing; set for `fintoc_pac` (company accounts fail), null otherwise."
                }
              },
              "required": [
                "rail",
                "rail_name",
                "recommended",
                "warning"
              ]
            },
            "description": "Rails that can be added now, card rails most preferred first and the PAC last: `fintoc_card` appears only while Fintoc has the product enabled for Vitrina (probed, no flag) and, while it does, `oneclick` and `mercadopago` are not listed; without it they stay as the card fallback. `fintoc_pac` only with «PAC permitido» on and Fintoc PAC available. `recommended` is the first card rail; the PAC is never recommended."
          },
          "pac_banks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "code",
                "name"
              ]
            },
            "description": "Banks where a PAC can be enrolled; send a `code` as `bank` when adding a PAC."
          }
        },
        "required": [
          "methods",
          "default_method_id",
          "pays_by",
          "pending_cobro",
          "transfer",
          "available_rails",
          "pac_banks"
        ]
      },
      "ClinicProfessional": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "native",
              "healthatom",
              "reservo"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "nombre": {
            "type": "string"
          },
          "apellidos": {
            "type": "string"
          },
          "rut": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "especialidad": {
            "type": [
              "string",
              "null"
            ]
          },
          "especialidades_secundarias": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cargo": {
            "type": [
              "string",
              "null"
            ]
          },
          "registro": {
            "type": [
              "string",
              "null"
            ]
          },
          "sexo": {
            "type": [
              "string",
              "null"
            ]
          },
          "birthdate": {
            "type": [
              "string",
              "null"
            ]
          },
          "direccion": {
            "type": [
              "string",
              "null"
            ]
          },
          "agenda_online": {
            "type": "boolean"
          },
          "intervalo_minutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "charges_with": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "company_invoice",
              "fee_receipt"
            ],
            "description": "«Cobra con» — how the professional charges the clinic for her liquidación: `company_invoice` = factura de su sociedad (then `company_legal_name` and a check-digit-valid `company_rut` are required), `fee_receipt` = boleta de honorarios (the liquidación then shows the SII retención of the boleta’s year and the líquido). Null = not recorded. Vitrina-owned: editable on any source; switching away from `company_invoice` clears the sociedad."
          },
          "company_legal_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "company_rut": {
            "type": [
              "string",
              "null"
            ]
          },
          "user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "schedule": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "inicio": {
                    "type": "string"
                  },
                  "fin": {
                    "type": "string"
                  }
                },
                "required": [
                  "inicio",
                  "fin"
                ]
              }
            }
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "source",
          "external_id",
          "nombre",
          "apellidos",
          "rut",
          "email",
          "phone",
          "especialidad",
          "especialidades_secundarias",
          "cargo",
          "registro",
          "sexo",
          "birthdate",
          "direccion",
          "agenda_online",
          "intervalo_minutes",
          "active",
          "description",
          "charges_with",
          "company_legal_name",
          "company_rut",
          "user_id",
          "schedule",
          "synced_at",
          "created_at",
          "updated_at"
        ]
      },
      "ClinicProfessionalDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ClinicProfessional"
          },
          {
            "type": "object",
            "properties": {
              "data": {
                "type": "object",
                "additionalProperties": {}
              },
              "citas_next_7d": {
                "type": "number"
              }
            },
            "required": [
              "data",
              "citas_next_7d"
            ]
          }
        ]
      },
      "ClinicService": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "native",
              "healthatom",
              "reservo"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "nombre": {
            "type": "string"
          },
          "codigo": {
            "type": [
              "string",
              "null"
            ]
          },
          "categoria": {
            "type": [
              "string",
              "null"
            ]
          },
          "tipo": {
            "type": [
              "string",
              "null"
            ]
          },
          "precio": {
            "type": [
              "number",
              "null"
            ]
          },
          "arancel": {
            "type": [
              "string",
              "null"
            ]
          },
          "arancel_external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "arancel_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "vendor_duration_minutes": {
            "type": [
              "number",
              "null"
            ]
          },
          "review_state": {
            "type": "string",
            "enum": [
              "stale",
              "draft",
              "reviewed"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_telehealth": {
            "type": "boolean"
          },
          "online_bookable": {
            "type": "boolean"
          },
          "is_entry": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "agent_bookable": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "agent_bookable_effective": {
            "type": "boolean"
          },
          "lab_bearing": {
            "type": "boolean"
          },
          "requires_consent_template_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "eligibility_min_age_years": {
            "type": [
              "number",
              "null"
            ]
          },
          "eligibility_max_age_years": {
            "type": [
              "number",
              "null"
            ]
          },
          "eligibility_sexo": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "F",
              "M",
              "otro"
            ]
          },
          "eligibility_previsiones": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "active": {
            "type": "boolean"
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "source",
          "external_id",
          "nombre",
          "codigo",
          "categoria",
          "tipo",
          "precio",
          "arancel",
          "arancel_external_id",
          "arancel_name",
          "duration_minutes",
          "vendor_duration_minutes",
          "review_state",
          "reviewed_at",
          "reviewed_by",
          "is_telehealth",
          "online_bookable",
          "is_entry",
          "agent_bookable",
          "agent_bookable_effective",
          "lab_bearing",
          "requires_consent_template_id",
          "eligibility_min_age_years",
          "eligibility_max_age_years",
          "eligibility_sexo",
          "eligibility_previsiones",
          "active",
          "synced_at"
        ]
      },
      "ClinicServiceArancel": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "active": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "external_id",
          "name",
          "total",
          "active"
        ]
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "secret": {
            "type": "string",
            "description": "The full `whsec_…` secret on the CREATE response only; every other read returns its first nine characters."
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "enabled": {
            "type": "boolean",
            "description": "Your own switch. `false` = nothing is delivered."
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "include_data": {
            "type": "boolean",
            "description": "«Incluir datos del recurso»."
          },
          "owner_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "member",
              "api_key"
            ],
            "description": "Who owns it: the member or API key that created it or last changed its url, events or `include_data`. `null` only on subscriptions created before owners existed (read as the workspace, unrestricted)."
          },
          "owner_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The owner — the member's user id, or the API key id (a personal token acts as its member)."
          },
          "paused_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when the platform AUTO-PAUSED the subscription (20 failed deliveries in a row, or 24 h failing). Paused = nothing is delivered until `POST /webhooks/{id}/resume`."
          },
          "paused_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "consecutive_failures",
              "failing_24h"
            ]
          },
          "last_delivery_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_status": {
            "type": [
              "integer",
              "null"
            ]
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Failed ATTEMPTS in a row (every retry counts). Reset by a success."
          },
          "consecutive_failed_deliveries": {
            "type": "integer",
            "description": "Failed DELIVERIES in a row — events whose every retry failed. 20 pauses the subscription. Reset by a success."
          },
          "failing_since": {
            "type": [
              "string",
              "null"
            ],
            "description": "First failed attempt of the current streak; `null` after any success. 24 h pauses the subscription."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "url",
          "secret",
          "events",
          "enabled",
          "description",
          "include_data",
          "owner_kind",
          "owner_id",
          "paused_at",
          "paused_reason",
          "last_delivery_at",
          "last_status",
          "consecutive_failures",
          "consecutive_failed_deliveries",
          "failing_since",
          "created_at",
          "updated_at"
        ]
      },
      "WebhookEventDoc": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "enum": [
              "ai_agent.publish",
              "ai_agent_graph.publish",
              "agent_scenario_run.completed",
              "agent_scenario_suite.completed",
              "conversation.created",
              "conversation.assigned",
              "conversation.resolved",
              "message.received",
              "message.sent",
              "ticket.opened",
              "ticket.reopened",
              "ticket.resolved",
              "ticket.closed",
              "sla.breached",
              "kb.source.create",
              "kb.source.embed",
              "kb.source.update",
              "kb.source.delete",
              "kb_file.uploaded",
              "kb_file.deleted",
              "skill.created",
              "skill.updated",
              "skill.deleted",
              "custom_tool.created",
              "custom_tool.updated",
              "custom_tool.deleted",
              "macro.applied",
              "cost.budget_alert",
              "runtime.budget_exceeded",
              "lead.created",
              "lead.stage_changed",
              "lead.pipeline_changed",
              "lead.assigned",
              "lead.won",
              "lead.lost",
              "lead.unqualified",
              "order.created",
              "order.status_changed",
              "stock.synced",
              "stock.source_retired",
              "vehicle.published",
              "vehicle.unpublished",
              "vehicle.publish_failed",
              "vehicle.created",
              "vehicle.attachment.created",
              "mercadolibre.order",
              "mercadolibre.vis_lead",
              "chileautos.lead",
              "yapo.lead",
              "website.lead",
              "demo_lead.created",
              "demo_lead.pending_scheduling",
              "demo_lead.routed",
              "demo_booking.booked",
              "demo_booking.rescheduled",
              "demo_booking.cancelled",
              "demo_booking.held",
              "demo_booking.no_show",
              "consignment.candidate.created",
              "quote.issued",
              "quote.voided",
              "quote.extended",
              "quote.archived",
              "reservation.created",
              "reservation.voided",
              "reservation.abono_disposed",
              "reservation.converted",
              "sale_note.issued",
              "sale_note.approved",
              "sale_note.voided",
              "purchase_note.issued",
              "purchase_note.corrected",
              "purchase_note.voided",
              "payment.recorded",
              "consignment.created",
              "consignment.returned",
              "consignment.sold",
              "credit_application.recorded",
              "credit_application.submitted",
              "credit_application.decided",
              "credit_application.withdrawn",
              "price_approval.requested",
              "price_approval.decided",
              "contact.created",
              "contact.updated",
              "contact.merged",
              "appointment.booked",
              "appointment.rescheduled",
              "appointment.cancelled",
              "appointment.completed",
              "appointment.no_show",
              "appointment.reminded",
              "appointment.imported",
              "campaign.sent",
              "campaign.paused",
              "sending_domain.verified",
              "coach.proposals_ready",
              "coach.finding_ready",
              "voice.autonomous_call",
              "safety_hold.placed",
              "safety_hold.ack_overdue",
              "safety_hold.first_response_overdue",
              "team.created",
              "team.updated",
              "team.deleted",
              "custom_role.created",
              "custom_role.updated",
              "custom_role.deleted",
              "macro.created",
              "macro.updated",
              "macro.deleted",
              "assignment_rule.created",
              "assignment_rule.updated",
              "assignment_rule.deleted",
              "sla_policy.created",
              "sla_policy.updated",
              "sla_policy.deleted",
              "automation_trigger.created",
              "automation_trigger.updated",
              "automation_trigger.deleted",
              "sending_domain.created",
              "sending_domain.deleted",
              "whatsapp_template.created",
              "whatsapp_template.status_changed",
              "whatsapp_template.deleted",
              "whatsapp_flow.created",
              "whatsapp_flow.published",
              "whatsapp_flow.deprecated",
              "whatsapp_flow.deleted",
              "whatsapp_flow.completed",
              "channel.connected",
              "channel.updated",
              "channel.disconnected",
              "membership.created",
              "membership.updated",
              "membership.removed",
              "clinic_professional.created",
              "clinic_professional.updated",
              "clinic_professional.deleted",
              "clinic_service.created",
              "clinic_service.updated",
              "clinic_service.deleted",
              "clinic_price_list.updated",
              "clinic_pack_purchase.created",
              "clinic_pack_purchase.session_consumed",
              "clinic_pack_purchase.cancelled",
              "clinic_budget.created",
              "clinic_budget.sent",
              "clinic_budget.accepted",
              "clinic_budget.rejected",
              "clinic_budget.voided",
              "clinic_budget.mirror_changed",
              "clinic_cash_session.opened",
              "clinic_cash_session.closed",
              "clinic_patient.created",
              "clinic_patient.updated",
              "clinic_patient.deleted",
              "clinic_patient.merged",
              "clinic_encounter.opened",
              "clinic_encounter.closed",
              "clinic_note.signed",
              "clinic_note.amended",
              "clinic_consent.signed",
              "clinic_consent.revoked",
              "clinic_document.uploaded",
              "clinic_document.deleted",
              "clinic_privacy_request.completed"
            ]
          },
          "version": {
            "type": "integer",
            "description": "The schema version of this event type — every delivery carries it as `version`. Bumped only on a breaking change to `data`."
          },
          "resource_type": {
            "type": "string",
            "description": "What the event is about — the `resource.type` of every delivery (`lead`, `vehicle`, `contact`, …)."
          },
          "read_scope": {
            "type": "string",
            "description": "The scope a subscription OWNER must hold, with unrestricted visibility, for «Incluir datos del recurso» to deliver `data`. Otherwise the delivery carries `data_omitted`."
          },
          "sensitive": {
            "type": "boolean",
            "description": "About a dato sensible (health data): always delivered as the notice, whatever the subscription asked for."
          },
          "description": {
            "type": "string",
            "description": "What happened, in a sentence, plus anything a consumer must do about it."
          },
          "fires_when": {
            "type": "string",
            "description": "The exact producer condition — so you can reason about ordering and duplicates."
          },
          "data_schema": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Field name → an informal type (\"string (uuid)\", \"integer | null\", \"enum: a|b|c\"). Documentation, not a JSON Schema: the envelope is stable, individual fields may gain siblings."
          },
          "sample": {
            "type": "object",
            "additionalProperties": {},
            "description": "A realistic `data` object — one delivery body's `data` field."
          }
        },
        "required": [
          "name",
          "version",
          "resource_type",
          "read_scope",
          "sensitive",
          "description",
          "fires_when",
          "data_schema",
          "sample"
        ]
      },
      "WebhookSigningDocs": {
        "type": "object",
        "properties": {
          "envelope": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "data_modes": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "The two delivery modes (`notice`, `data`) and every `data_omitted` reason."
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "signature_scheme": {
            "type": "string"
          },
          "signature_format": {
            "type": "string"
          },
          "verify_pseudocode": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "timing_window_seconds": {
            "type": "integer"
          },
          "retry": {
            "type": "object",
            "properties": {
              "attempts": {
                "type": "integer"
              },
              "backoff": {
                "type": "string"
              },
              "delays_seconds": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "attempts",
              "backoff",
              "delays_seconds",
              "note"
            ]
          },
          "auto_pause": {
            "type": "object",
            "properties": {
              "consecutive_failed_deliveries": {
                "type": "integer"
              },
              "failing_hours": {
                "type": "integer"
              },
              "note": {
                "type": "string"
              }
            },
            "required": [
              "consecutive_failed_deliveries",
              "failing_hours",
              "note"
            ]
          }
        },
        "required": [
          "envelope",
          "data_modes",
          "headers",
          "signature_scheme",
          "signature_format",
          "verify_pseudocode",
          "timing_window_seconds",
          "retry",
          "auto_pause"
        ]
      },
      "WebhookEventCatalog": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventDoc"
            }
          },
          "signing": {
            "$ref": "#/components/schemas/WebhookSigningDocs"
          }
        },
        "required": [
          "events",
          "signing"
        ]
      },
      "CreateWebhookBody": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "*",
                "ai_agent.publish",
                "ai_agent_graph.publish",
                "agent_scenario_run.completed",
                "agent_scenario_suite.completed",
                "conversation.created",
                "conversation.assigned",
                "conversation.resolved",
                "message.received",
                "message.sent",
                "ticket.opened",
                "ticket.reopened",
                "ticket.resolved",
                "ticket.closed",
                "sla.breached",
                "kb.source.create",
                "kb.source.embed",
                "kb.source.update",
                "kb.source.delete",
                "kb_file.uploaded",
                "kb_file.deleted",
                "skill.created",
                "skill.updated",
                "skill.deleted",
                "custom_tool.created",
                "custom_tool.updated",
                "custom_tool.deleted",
                "macro.applied",
                "cost.budget_alert",
                "runtime.budget_exceeded",
                "lead.created",
                "lead.stage_changed",
                "lead.pipeline_changed",
                "lead.assigned",
                "lead.won",
                "lead.lost",
                "lead.unqualified",
                "order.created",
                "order.status_changed",
                "stock.synced",
                "stock.source_retired",
                "vehicle.published",
                "vehicle.unpublished",
                "vehicle.publish_failed",
                "vehicle.created",
                "vehicle.attachment.created",
                "mercadolibre.order",
                "mercadolibre.vis_lead",
                "chileautos.lead",
                "yapo.lead",
                "website.lead",
                "demo_lead.created",
                "demo_lead.pending_scheduling",
                "demo_lead.routed",
                "demo_booking.booked",
                "demo_booking.rescheduled",
                "demo_booking.cancelled",
                "demo_booking.held",
                "demo_booking.no_show",
                "consignment.candidate.created",
                "quote.issued",
                "quote.voided",
                "quote.extended",
                "quote.archived",
                "reservation.created",
                "reservation.voided",
                "reservation.abono_disposed",
                "reservation.converted",
                "sale_note.issued",
                "sale_note.approved",
                "sale_note.voided",
                "purchase_note.issued",
                "purchase_note.corrected",
                "purchase_note.voided",
                "payment.recorded",
                "consignment.created",
                "consignment.returned",
                "consignment.sold",
                "credit_application.recorded",
                "credit_application.submitted",
                "credit_application.decided",
                "credit_application.withdrawn",
                "price_approval.requested",
                "price_approval.decided",
                "contact.created",
                "contact.updated",
                "contact.merged",
                "appointment.booked",
                "appointment.rescheduled",
                "appointment.cancelled",
                "appointment.completed",
                "appointment.no_show",
                "appointment.reminded",
                "appointment.imported",
                "campaign.sent",
                "campaign.paused",
                "sending_domain.verified",
                "coach.proposals_ready",
                "coach.finding_ready",
                "voice.autonomous_call",
                "safety_hold.placed",
                "safety_hold.ack_overdue",
                "safety_hold.first_response_overdue",
                "team.created",
                "team.updated",
                "team.deleted",
                "custom_role.created",
                "custom_role.updated",
                "custom_role.deleted",
                "macro.created",
                "macro.updated",
                "macro.deleted",
                "assignment_rule.created",
                "assignment_rule.updated",
                "assignment_rule.deleted",
                "sla_policy.created",
                "sla_policy.updated",
                "sla_policy.deleted",
                "automation_trigger.created",
                "automation_trigger.updated",
                "automation_trigger.deleted",
                "sending_domain.created",
                "sending_domain.deleted",
                "whatsapp_template.created",
                "whatsapp_template.status_changed",
                "whatsapp_template.deleted",
                "whatsapp_flow.created",
                "whatsapp_flow.published",
                "whatsapp_flow.deprecated",
                "whatsapp_flow.deleted",
                "whatsapp_flow.completed",
                "channel.connected",
                "channel.updated",
                "channel.disconnected",
                "membership.created",
                "membership.updated",
                "membership.removed",
                "clinic_professional.created",
                "clinic_professional.updated",
                "clinic_professional.deleted",
                "clinic_service.created",
                "clinic_service.updated",
                "clinic_service.deleted",
                "clinic_price_list.updated",
                "clinic_pack_purchase.created",
                "clinic_pack_purchase.session_consumed",
                "clinic_pack_purchase.cancelled",
                "clinic_budget.created",
                "clinic_budget.sent",
                "clinic_budget.accepted",
                "clinic_budget.rejected",
                "clinic_budget.voided",
                "clinic_budget.mirror_changed",
                "clinic_cash_session.opened",
                "clinic_cash_session.closed",
                "clinic_patient.created",
                "clinic_patient.updated",
                "clinic_patient.deleted",
                "clinic_patient.merged",
                "clinic_encounter.opened",
                "clinic_encounter.closed",
                "clinic_note.signed",
                "clinic_note.amended",
                "clinic_consent.signed",
                "clinic_consent.revoked",
                "clinic_document.uploaded",
                "clinic_document.deleted",
                "clinic_privacy_request.completed"
              ]
            },
            "minItems": 1,
            "maxItems": 20
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "include_data": {
            "type": "boolean",
            "description": "«Incluir datos del recurso». `false` (the default) = every delivery is the notice: resource, changes, author, time and `url`, with `data_omitted: \"not_requested\"`. `true` = each delivery also carries `data` when, at dispatch time, this subscription's OWNER — whoever created it or last changed its url, events or this flag — holds the resource read scope (`read_scope` in the catalogue) and sees every record; otherwise `data_omitted` says why. An event about a dato sensible is always the notice."
          }
        },
        "required": [
          "url",
          "events"
        ],
        "example": {
          "url": "https://crm.example.cl/vitrina",
          "events": [
            "lead.created",
            "lead.stage_changed"
          ],
          "description": "CRM sync",
          "include_data": false
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Pass to `POST …/deliveries/{deliveryId}/redeliver`."
          },
          "subscription_id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "type": "string"
          },
          "event_id": {
            "type": "string",
            "format": "uuid",
            "description": "The event id — identical across every attempt and redelivery of one event."
          },
          "attempt": {
            "type": "integer"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "`http_<code>`, `timeout`, or the network error; `null` on success."
          },
          "response_excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_payload": {
            "type": "object",
            "additionalProperties": {},
            "description": "The exact body that was POSTed."
          },
          "latency_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "redelivery_of": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set on a manual redelivery: the delivery row it repeated."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "subscription_id",
          "event",
          "event_id",
          "attempt",
          "status_code",
          "error",
          "response_excerpt",
          "request_payload",
          "latency_ms",
          "redelivery_of",
          "created_at"
        ]
      },
      "WebhookRedeliverResult": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "format": "uuid"
          },
          "delivery_id": {
            "type": "integer"
          },
          "event": {
            "type": "string"
          },
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued"
            ]
          }
        },
        "required": [
          "subscription_id",
          "delivery_id",
          "event",
          "event_id",
          "status"
        ]
      },
      "LeadActivity": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "lead_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "description": "`created`, `stage_changed`, `assigned`, `value_updated`, `won`, `lost`, …"
          },
          "data": {
            "type": "object",
            "additionalProperties": {}
          },
          "actor_user_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member who acted, when a member did. Prefer `author`."
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "lead_id",
          "kind",
          "data",
          "actor_user_id",
          "conversation_id",
          "author",
          "created_at"
        ]
      },
      "ContactTagRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "display_name": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "display_name"
        ]
      },
      "ContactSearchRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "display_name": {
            "type": "string"
          },
          "named": {
            "type": "boolean"
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "lead_sources": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tags": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactTagRef"
            }
          }
        },
        "required": [
          "id",
          "name",
          "display_name",
          "named",
          "channels",
          "lead_sources",
          "tags"
        ],
        "additionalProperties": {}
      },
      "AddPaymentMethodResult": {
        "type": "object",
        "properties": {
          "method_id": {
            "type": "string",
            "format": "uuid"
          },
          "rail": {
            "type": "string",
            "enum": [
              "fintoc_pac",
              "fintoc_card",
              "oneclick",
              "mercadopago"
            ],
            "description": "The rail that holds the method: `fintoc_pac` (automatic debit from a current account through Fintoc), `fintoc_card` (credit or debit card on file at Fintoc), `oneclick` (card on file at Transbank Webpay Oneclick), `mercadopago` (card on file at Mercado Pago)."
          },
          "status": {
            "type": "string",
            "enum": [
              "activo",
              "pendiente"
            ]
          },
          "redirect": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "url": {
                "type": "string",
                "format": "uri"
              },
              "method": {
                "type": "string",
                "enum": [
                  "GET",
                  "POST"
                ]
              },
              "fields": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "url",
              "method",
              "fields"
            ],
            "description": "Where to send the browser now: a GET to the PAC checkout, or a form POST of `fields` (TBK_TOKEN) to Transbank. Null when the method is already active."
          }
        },
        "required": [
          "method_id",
          "rail",
          "status",
          "redirect"
        ]
      },
      "AddPaymentMethodBody": {
        "type": "object",
        "properties": {
          "rail": {
            "type": "string",
            "enum": [
              "fintoc_pac",
              "fintoc_card",
              "oneclick",
              "mercadopago"
            ],
            "description": "The rail that holds the method: `fintoc_pac` (automatic debit from a current account through Fintoc), `fintoc_card` (credit or debit card on file at Fintoc), `oneclick` (card on file at Transbank Webpay Oneclick), `mercadopago` (card on file at Mercado Pago)."
          },
          "return_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "description": "Where the provider sends the browser back — must be on the app’s own origin (the Facturación page). Required for `fintoc_pac`, `fintoc_card` and `oneclick`."
          },
          "cancel_url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri",
            "description": "`fintoc_pac` / `fintoc_card`: where a cancelled enrolment lands (defaults to `return_url`)."
          },
          "bank": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "`fintoc_pac`: a bank `code` from `pac_banks`; the enrolment opens on it."
          },
          "email": {
            "type": "string",
            "maxLength": 320,
            "format": "email",
            "description": "The payer email. Defaults to the workspace’s billing email."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 256
          },
          "rut": {
            "type": "string",
            "minLength": 1,
            "maxLength": 32
          },
          "card": {
            "type": "object",
            "properties": {
              "token": {
                "type": "string",
                "minLength": 1,
                "maxLength": 256
              },
              "brand": {
                "type": "string",
                "minLength": 1,
                "maxLength": 32
              },
              "last4": {
                "type": "string",
                "pattern": "^[0-9]{4}$"
              },
              "exp_month": {
                "type": "integer",
                "minimum": 1,
                "maximum": 12
              },
              "exp_year": {
                "type": "integer",
                "minimum": 2000,
                "maximum": 2100
              }
            },
            "required": [
              "token"
            ],
            "description": "`mercadopago`: the card token minted by Mercado Pago’s own SDK, with the brand, last 4 and expiry it reports (display only). Never a card number."
          },
          "pay_invoice_id": {
            "type": "string",
            "format": "uuid",
            "description": "`mercadopago`: «Pagar ahora» — an unpaid cobro of this workspace to collect from the new card right away. The card’s subscription then starts its cycle within about an hour (12:00 Santiago at the earliest) and is armed with that cobro, instead of waiting for the cobro’s scheduled day."
          }
        },
        "required": [
          "rail"
        ]
      },
      "TransferClaimResult": {
        "type": "object",
        "properties": {
          "created": {
            "type": "boolean",
            "description": "False when a claim was already pending (idempotent)."
          },
          "claimedAt": {
            "type": "string"
          },
          "number": {
            "type": "string"
          },
          "comprobante": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ClaimComprobante"
              },
              {
                "description": "#7094: the comprobante attached by THIS call (null when the call sent none, or it was not stored)."
              }
            ]
          },
          "comprobante_error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "comprobante_not_stored",
                  "no_pending_claim"
                ]
              },
              "message": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean"
              }
            },
            "required": [
              "reason",
              "message",
              "retryable"
            ],
            "description": "#7094: set only when a file was sent and NOT stored. The claim was still made (and is being verified): offer «reintentar adjuntar» through `POST …/transfer-claim/comprobante` when `retryable`, never a second claim."
          }
        },
        "required": [
          "created",
          "claimedAt",
          "number",
          "comprobante",
          "comprobante_error"
        ]
      },
      "OverheadPeriod": {
        "type": "string",
        "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
        "example": "2026-01"
      },
      "GatewayPaymentRef": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "mercadopago",
              "webpay",
              "fintoc",
              "flow",
              "khipu",
              "bancame"
            ]
          },
          "payment_id": {
            "type": "string",
            "description": "The provider’s own payment id (Mercado Pago payment id; Webpay transaction token; Flow token; Khipu payment id; banca.me externalTrxId, which is the checkout id)."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Where to look the payment up: the clinic’s Mercado Pago activity search, or the provider’s site for Webpay, Flow, Khipu and banca.me (no per-payment page)."
          },
          "status": {
            "type": "string",
            "enum": [
              "approved",
              "partially_refunded",
              "replaced",
              "refunded",
              "charged_back",
              "reversed"
            ],
            "description": "`approved` = the money is in; `partially_refunded` = part went back (`amount_clp` is the net, `refunded_clp` what was refunded); `replaced` = superseded by the net row after a partial refund; `refunded` = returned from Mercado Pago; `charged_back` = the payer’s bank took it back; `reversed` = reversed in Vitrina for another reason. Only `approved` / `partially_refunded` are counted."
          },
          "refunded_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What Mercado Pago refunded of this payment, when known."
          }
        },
        "required": [
          "provider",
          "payment_id",
          "url",
          "status",
          "refunded_clp"
        ],
        "description": "A Mercado Pago checkout payment: its payment id, a link to it and its status. Null for every other movement."
      },
      "ClinicContactPayment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The movement row’s own id — a mirrored pago, a mirrored devolución or a native ledger payment (`kind` + `origin` say which)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "payment",
              "refund"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "dentalink",
              "medilink",
              "reservo",
              "vitrina"
            ]
          },
          "paid_on": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "amount_clp": {
            "type": "integer",
            "description": "Always positive; `kind` gives the sign."
          },
          "method": {
            "type": [
              "string",
              "null"
            ],
            "description": "The medio's display label: the vendor's (the clinic's /medios catalog, or Reservo's fixed labels) on mirrored rows, the ledger instrument's («Mercado Pago», «Transferencia») on native rows."
          },
          "instrument": {
            "type": [
              "string",
              "null"
            ],
            "description": "Native ledger instrument code (efectivo, transferencia, tarjeta_*, bono, …); null on mirrored rows."
          },
          "folio": {
            "type": [
              "string",
              "null"
            ]
          },
          "voided": {
            "type": "boolean",
            "description": "Anulado / reversed: shown, never counted in `summary`."
          },
          "patient_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "patient_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Filled only when the contact speaks for several patients."
          },
          "concept": {
            "type": [
              "string",
              "null"
            ]
          },
          "professional": {
            "type": [
              "string",
              "null"
            ]
          },
          "appointment_starts_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "boleta": {
            "type": "string",
            "enum": [
              "available",
              "unknown",
              "none"
            ],
            "description": "Its boleta: `available` = `GET …/payments/{paymentId}/boleta` serves the PDF; `unknown` = never asked the practice system yet (Dentalink / Medilink) — the same call asks and remembers; `none` = there is no boleta (refunds always)."
          },
          "gateway": {
            "$ref": "#/components/schemas/GatewayPaymentRef"
          }
        },
        "required": [
          "id",
          "kind",
          "origin",
          "paid_on",
          "amount_clp",
          "method",
          "instrument",
          "folio",
          "voided",
          "patient_id",
          "patient_name",
          "concept",
          "professional",
          "appointment_starts_at",
          "boleta",
          "gateway"
        ]
      },
      "ClinicContactPaymentsView": {
        "type": "object",
        "properties": {
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "object",
            "properties": {
              "vendor": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "dentalink",
                  "medilink",
                  "reservo"
                ],
                "description": "Practice system that authors (some of) this clinic's money; null = native Vitrina Clínica."
              },
              "mirrored": {
                "type": "boolean"
              },
              "mirror_enabled": {
                "type": "boolean"
              },
              "mirror_from": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "last_synced_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "vendor",
              "mirrored",
              "mirror_enabled",
              "mirror_from",
              "last_synced_at"
            ]
          },
          "mirror_missing": {
            "type": "boolean",
            "description": "A connected vendor clinic whose money is NOT mirrored: the history is only what Vitrina recorded natively."
          },
          "patients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "relationship": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "name",
                "relationship"
              ]
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total_paid_clp": {
                "type": "integer"
              },
              "payment_count": {
                "type": "integer"
              },
              "last_paid_on": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "outstanding_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "total_paid_clp",
              "payment_count",
              "last_paid_on",
              "outstanding_clp"
            ]
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicContactPayment"
            }
          },
          "has_more": {
            "type": "boolean"
          }
        },
        "required": [
          "contact_id",
          "source",
          "mirror_missing",
          "patients",
          "summary",
          "payments",
          "has_more"
        ]
      },
      "ClinicDiagnostico": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "not_started",
              "active",
              "closed"
            ]
          },
          "viewer": {
            "type": "object",
            "properties": {
              "may_start": {
                "type": "boolean"
              }
            },
            "required": [
              "may_start"
            ]
          },
          "eligibility": {
            "type": "object",
            "properties": {
              "can_start": {
                "type": "boolean"
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "no_account",
                  "not_dentalink",
                  "money_adopted",
                  "dpa_required",
                  "plan_active",
                  "already_used",
                  "active"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "available_on": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "can_start",
              "reason",
              "message",
              "available_on"
            ]
          },
          "window": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "type": "string",
                "enum": [
                  "active",
                  "closed"
                ]
              },
              "phase": {
                "type": "string",
                "enum": [
                  "running",
                  "closing",
                  "closed"
                ]
              },
              "closing_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "plan_pending",
                  "finishing"
                ]
              },
              "held_until": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "started_at": {
                "type": "string"
              },
              "ends_at": {
                "type": "string"
              },
              "day": {
                "type": "integer"
              },
              "days_total": {
                "type": "integer"
              },
              "days_left": {
                "type": "integer"
              },
              "warned_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ended_early_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "closed_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "outcome": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "purged",
                  "kept_plan",
                  "kept_preexisting"
                ]
              },
              "mirror_origin": {
                "type": "string",
                "enum": [
                  "diagnostico",
                  "preexisting"
                ]
              },
              "closing": {
                "type": "object",
                "properties": {
                  "deletes_detail": {
                    "type": "boolean"
                  },
                  "sentence": {
                    "type": "string"
                  }
                },
                "required": [
                  "deletes_detail",
                  "sentence"
                ]
              },
              "runs": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "outcome": {
                      "type": "string",
                      "enum": [
                        "purged",
                        "residual_purged",
                        "kept_plan",
                        "kept_preexisting",
                        "deferred_plan_pending",
                        "failed"
                      ]
                    },
                    "finished_at": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "object",
                      "properties": {
                        "plans": {
                          "type": "integer"
                        },
                        "plan_lines": {
                          "type": "integer"
                        },
                        "payments": {
                          "type": "integer"
                        },
                        "tax_documents": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "plans",
                        "plan_lines",
                        "payments",
                        "tax_documents"
                      ]
                    }
                  },
                  "required": [
                    "outcome",
                    "finished_at",
                    "deleted"
                  ]
                }
              }
            },
            "required": [
              "id",
              "status",
              "phase",
              "closing_reason",
              "held_until",
              "started_at",
              "ends_at",
              "day",
              "days_total",
              "days_left",
              "warned_at",
              "ended_early_at",
              "closed_at",
              "outcome",
              "mirror_origin",
              "closing",
              "runs"
            ]
          },
          "sync": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "copying",
                  "up_to_date",
                  "error",
                  "off"
                ]
              },
              "synced_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "state",
              "synced_at",
              "message"
            ]
          },
          "report": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version": {
                "type": "number",
                "enum": [
                  1
                ]
              },
              "computed_at": {
                "type": "string"
              },
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "data_through": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "state": {
                "type": "string",
                "enum": [
                  "ready",
                  "settling",
                  "empty"
                ]
              },
              "state_reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "collected": {
                "type": "object",
                "properties": {
                  "clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "previous_clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "delta_pct": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "clp",
                  "previous_clp",
                  "delta_pct",
                  "rule"
                ]
              },
              "acceptance": {
                "type": "object",
                "properties": {
                  "presented": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "accepted": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "rate": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "presented_clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "accepted_clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "formula_status": {
                    "type": "string",
                    "enum": [
                      "acordada",
                      "propuesta"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "presented",
                  "accepted",
                  "rate",
                  "presented_clp",
                  "accepted_clp",
                  "formula_status",
                  "rule"
                ]
              },
              "cierres": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "value_clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "formula_status": {
                    "type": "string",
                    "enum": [
                      "acordada",
                      "propuesta"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "count",
                  "value_clp",
                  "formula_status",
                  "rule"
                ]
              },
              "by_professional": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "professional_id": {
                      "type": "string"
                    },
                    "professional_name": {
                      "type": "string"
                    },
                    "presented": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "accepted": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "rate": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "accepted_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "cierres": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "cierres_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "suppressed": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "professional_id",
                    "professional_name",
                    "presented",
                    "accepted",
                    "rate",
                    "accepted_clp",
                    "cierres",
                    "cierres_clp",
                    "suppressed"
                  ]
                }
              },
              "months": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string"
                    },
                    "accepted_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "produced_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "collected_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "cierres": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "month",
                    "accepted_clp",
                    "produced_clp",
                    "collected_clp",
                    "cierres"
                  ]
                }
              },
              "first_visit": {
                "type": "object",
                "properties": {
                  "patients": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "paid": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "rate": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "formula_status": {
                    "type": "string",
                    "enum": [
                      "acordada",
                      "propuesta"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "patients",
                  "paid",
                  "rate",
                  "formula_status",
                  "rule"
                ]
              },
              "second_visit": {
                "type": "object",
                "properties": {
                  "eligible": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "converted": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "rate": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "formula_status": {
                    "type": "string",
                    "enum": [
                      "acordada",
                      "propuesta"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "eligible",
                  "converted",
                  "rate",
                  "formula_status",
                  "rule"
                ]
              },
              "botados": {
                "type": "object",
                "properties": {
                  "unaccepted": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "suppressed": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "count",
                      "clp",
                      "suppressed"
                    ]
                  },
                  "accepted_unscheduled": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "suppressed": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "count",
                      "clp",
                      "suppressed"
                    ]
                  },
                  "mid_treatment_no_future_cita": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "suppressed": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "count",
                      "clp",
                      "suppressed"
                    ]
                  },
                  "total": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "suppressed": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "count",
                      "clp",
                      "suppressed"
                    ]
                  },
                  "focus": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "formula_status": {
                    "type": "string",
                    "enum": [
                      "acordada",
                      "propuesta"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "unaccepted",
                  "accepted_unscheduled",
                  "mid_treatment_no_future_cita",
                  "total",
                  "focus",
                  "formula_status",
                  "rule"
                ]
              },
              "band": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "band": {
                    "type": "string"
                  },
                  "count": {
                    "type": "number"
                  },
                  "has_history": {
                    "type": "boolean"
                  },
                  "months": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "count": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "month",
                        "count"
                      ]
                    }
                  },
                  "uf_month": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "estimated_clp_month": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "price_label": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "rule": {
                    "type": "string"
                  }
                },
                "required": [
                  "band",
                  "count",
                  "has_history",
                  "months",
                  "uf_month",
                  "estimated_clp_month",
                  "price_label",
                  "rule"
                ]
              }
            },
            "required": [
              "version",
              "computed_at",
              "from",
              "to",
              "data_through",
              "state",
              "state_reason",
              "collected",
              "acceptance",
              "cierres",
              "by_professional",
              "months",
              "first_visit",
              "second_visit",
              "botados",
              "band"
            ]
          },
          "report_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "live",
              "stored"
            ]
          },
          "plan": {
            "type": "object",
            "properties": {
              "holds_plan": {
                "type": "boolean"
              },
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "awaiting_payment",
                  "active",
                  "past_due"
                ]
              }
            },
            "required": [
              "holds_plan",
              "status"
            ]
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "started_at": {
                  "type": "string"
                },
                "closed_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "outcome": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "purged",
                    "kept_plan",
                    "kept_preexisting"
                  ]
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "started_at",
                "closed_at",
                "outcome",
                "label"
              ]
            }
          },
          "suppression": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "min_count": {
                "type": "integer"
              }
            },
            "required": [
              "min_count"
            ]
          }
        },
        "required": [
          "status",
          "viewer",
          "eligibility",
          "window",
          "sync",
          "report",
          "report_source",
          "plan",
          "history",
          "suppression"
        ]
      },
      "ClinicLinea": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "nombre": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "archived": {
            "type": "boolean"
          },
          "leads": {
            "type": "integer"
          },
          "open_leads": {
            "type": "integer"
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "match": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "seed",
                    "owner"
                  ]
                }
              },
              "required": [
                "id",
                "match",
                "source"
              ]
            }
          },
          "keywords": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "keyword": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "seed",
                    "owner"
                  ]
                }
              },
              "required": [
                "id",
                "keyword",
                "source"
              ]
            },
            "description": "Words that send a lead without an ad to this Línea. Seeded for dental clinics, edited by the owner."
          },
          "specialties": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "nombre"
              ]
            }
          }
        },
        "required": [
          "id",
          "nombre",
          "is_default",
          "archived",
          "leads",
          "open_leads",
          "rules",
          "keywords",
          "specialties"
        ]
      },
      "ClinicLineasOverview": {
        "type": "object",
        "properties": {
          "lineas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicLinea"
            }
          },
          "unmapped_specialties": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "nombre"
              ]
            },
            "description": "Roster specialties no Línea holds yet, for the owner to place."
          },
          "ads": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "external_id": {
                  "type": "string"
                },
                "campaign_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ad_set_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ad_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "linea_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "`null` = unmapped, listed for the owner to place."
                },
                "via": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "rule",
                    "specialty_map",
                    "specialty_name"
                  ]
                },
                "matched": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "conversations": {
                  "type": "integer"
                },
                "leads": {
                  "type": "integer"
                }
              },
              "required": [
                "external_id",
                "campaign_name",
                "ad_set_name",
                "ad_name",
                "linea_id",
                "via",
                "matched",
                "conversations",
                "leads"
              ]
            },
            "description": "Every ad that opened a conversation, with its Línea."
          }
        },
        "required": [
          "lineas",
          "unmapped_specialties",
          "ads"
        ]
      },
      "PaymentRegistration": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "ambiguous",
              "matched",
              "dismissed",
              "cancelled"
            ]
          },
          "vendor": {
            "type": "string",
            "enum": [
              "dentalink",
              "medilink",
              "reservo"
            ]
          },
          "vendor_label": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "mercadopago"
            ]
          },
          "provider_reference": {
            "type": "string",
            "description": "The Mercado Pago payment number."
          },
          "provider_payment_url": {
            "type": "string"
          },
          "customer_payment_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "amount_clp": {
            "type": "integer"
          },
          "paid_on": {
            "type": "string"
          },
          "patient": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "rut"
            ]
          },
          "concept": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kind": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "appointment_starts_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "label",
              "appointment_starts_at"
            ]
          },
          "medio": {
            "type": "object",
            "properties": {
              "code": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "label": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "label"
            ]
          },
          "matched": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "mirror_payment_id": {
                "type": "string",
                "format": "uuid"
              },
              "vendor_reference": {
                "type": "string",
                "description": "The practice system's own pago number."
              },
              "received_on": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "amount_clp": {
                "type": "integer"
              },
              "medio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "voided": {
                "type": "boolean"
              },
              "by": {
                "type": "string",
                "enum": [
                  "auto_unique",
                  "auto_medio",
                  "auto_reference",
                  "staff"
                ]
              },
              "at": {
                "type": "string"
              }
            },
            "required": [
              "mirror_payment_id",
              "vendor_reference",
              "received_on",
              "amount_clp",
              "medio",
              "voided",
              "by",
              "at"
            ]
          },
          "candidates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "mirror_payment_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "vendor_reference": {
                  "type": "string",
                  "description": "The practice system's own pago number."
                },
                "received_on": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "medio": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "voided": {
                  "type": "boolean"
                }
              },
              "required": [
                "mirror_payment_id",
                "vendor_reference",
                "received_on",
                "amount_clp",
                "medio",
                "voided"
              ]
            }
          },
          "dismissed_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "registered_elsewhere",
              "not_needed"
            ]
          },
          "overdue": {
            "type": "boolean"
          },
          "reminded_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "vendor",
          "vendor_label",
          "provider",
          "provider_reference",
          "provider_payment_url",
          "customer_payment_id",
          "contact_id",
          "amount_clp",
          "paid_on",
          "patient",
          "concept",
          "medio",
          "matched",
          "candidates",
          "dismissed_reason",
          "overdue",
          "reminded_at",
          "created_at"
        ]
      },
      "PaymentRegistrationSettings": {
        "type": "object",
        "properties": {
          "vendor": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "dentalink",
              "medilink",
              "reservo"
            ]
          },
          "vendor_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "mercadopago_medio": {
            "type": [
              "string",
              "null"
            ]
          },
          "suggested_medio": {
            "type": [
              "string",
              "null"
            ]
          },
          "medios": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "active": {
                  "type": "boolean"
                }
              },
              "required": [
                "code",
                "label",
                "active"
              ]
            }
          },
          "reminder_business_days": {
            "type": "integer"
          }
        },
        "required": [
          "vendor",
          "vendor_label",
          "mercadopago_medio",
          "suggested_medio",
          "medios",
          "reminder_business_days"
        ]
      },
      "BankFeedSources": {
        "type": "object",
        "properties": {
          "email": {
            "type": "object",
            "properties": {
              "available": {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              "billed": {
                "type": "boolean",
                "enum": [
                  false
                ]
              }
            },
            "required": [
              "available",
              "billed"
            ]
          },
          "fintoc": {
            "type": "object",
            "properties": {
              "configured": {
                "type": "boolean"
              },
              "entitled": {
                "type": "boolean"
              },
              "feature": {
                "type": "string",
                "enum": [
                  "bank_feed_fintoc"
                ]
              },
              "price_clp_per_bank_month": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "connected_banks": {
                "type": "number"
              }
            },
            "required": [
              "configured",
              "entitled",
              "feature",
              "price_clp_per_bank_month",
              "connected_banks"
            ]
          },
          "bci_api_market": {
            "type": "object",
            "properties": {
              "configured": {
                "type": "boolean",
                "enum": [
                  false
                ]
              },
              "reason": {
                "type": "string"
              }
            },
            "required": [
              "configured",
              "reason"
            ]
          }
        },
        "required": [
          "email",
          "fintoc",
          "bci_api_market"
        ]
      },
      "FintocAccountMatch": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string",
            "description": "Fintoc's account id."
          },
          "bank_account_id": {
            "type": "string",
            "description": "The Finanzas bank account that now stands for it."
          },
          "outcome": {
            "type": "string",
            "enum": [
              "kept",
              "adopted",
              "created"
            ],
            "description": "`kept`: this link already had it. `adopted`: the existing account with the same number and currency (created by the Chipax import or a cartola upload) took the link, with its movements. `created`: a new account."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "several_candidates",
              "different_bank",
              "different_holder",
              "already_fed"
            ],
            "description": "Why an account with the same number and currency was NOT adopted (a new one was created instead); null when nothing was in doubt."
          },
          "candidate_bank_account_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The existing accounts with the same number and currency."
          }
        },
        "required": [
          "account_id",
          "bank_account_id",
          "outcome",
          "reason",
          "candidate_bank_account_ids"
        ]
      },
      "BankFeedConnection": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "provider": {
            "type": "string",
            "enum": [
              "fintoc",
              "bci_api_market"
            ]
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "holder_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "number_masked": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "number_masked"
              ]
            }
          },
          "enabled": {
            "type": "boolean"
          },
          "connected_at": {
            "type": "string"
          },
          "disconnected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "sync_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FintocAccountMatch"
            },
            "description": "Connect answer only: what the link did with each of its accounts."
          }
        },
        "required": [
          "id",
          "provider",
          "institution_name",
          "holder_name",
          "accounts",
          "enabled",
          "connected_at",
          "disconnected_at",
          "last_sync_at",
          "last_sync_status",
          "last_sync_error",
          "sync_cursor"
        ]
      },
      "BankFeedFintocConnectBody": {
        "type": "object",
        "properties": {
          "exchange_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          },
          "link_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          }
        }
      },
      "BankFeedIngestResult": {
        "type": "object",
        "properties": {
          "drafts": {
            "type": "number"
          },
          "inserted": {
            "type": "number"
          },
          "deduped": {
            "type": "number"
          },
          "cross_source_twins": {
            "type": "number"
          }
        },
        "required": [
          "drafts",
          "inserted",
          "deduped",
          "cross_source_twins"
        ]
      },
      "BankFeedEmailPullBody": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          }
        },
        "required": [
          "from",
          "to"
        ]
      },
      "FinanzasAccountKind": {
        "type": "string",
        "enum": [
          "ingreso",
          "costo",
          "gasto",
          "no_operacional"
        ]
      },
      "FinanzasAccountBehavior": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "fijo",
          "variable"
        ]
      },
      "FinanzasAccountUsage": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "example": "2026-10"
          },
          "total_clp": {
            "type": "integer",
            "description": "Whole CLP, NET, signed as the Resultado counts the account in that month (parents roll up their children). A costo / gasto line with no legal document sits in the Resultado’s «Egresos sin documento», not here."
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "description": "Distinct documents and movements behind `total_clp` (the Resultado drill-down of this account)."
          },
          "last_used_month": {
            "type": [
              "string",
              "null"
            ],
            "description": "Latest `YYYY-MM` with any line on this account or a descendant, up to `month`."
          },
          "used_6m": {
            "type": "boolean",
            "description": "Any line in the 6 months ending at `month`, inclusive."
          }
        },
        "required": [
          "month",
          "total_clp",
          "count",
          "last_used_month",
          "used_6m"
        ],
        "description": "Present only when the list was asked with `usage_month`."
      },
      "FinanzasAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "$ref": "#/components/schemas/FinanzasAccountKind"
          },
          "behavior": {
            "$ref": "#/components/schemas/FinanzasAccountBehavior"
          },
          "no_document": {
            "type": "boolean",
            "description": "«No lleva documento»: money out classified here never has a legal document. It opens no «Falta documento» case and is not counted in «Egresos sin documento»."
          },
          "position": {
            "type": "integer"
          },
          "depth": {
            "type": "integer",
            "minimum": 1
          },
          "path": {
            "type": "string"
          },
          "seeded": {
            "type": "boolean"
          },
          "has_children": {
            "type": "boolean"
          },
          "archived_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/FinanzasAccountUsage"
          }
        },
        "required": [
          "id",
          "parent_id",
          "name",
          "description",
          "kind",
          "behavior",
          "no_document",
          "position",
          "depth",
          "path",
          "seeded",
          "has_children",
          "archived_at",
          "created_at",
          "updated_at"
        ]
      },
      "FinanzasAccounts": {
        "type": "object",
        "properties": {
          "accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasAccount"
            }
          }
        },
        "required": [
          "accounts"
        ]
      },
      "FinanzasAccountReview": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "in_progress": {
            "type": "boolean",
            "description": "The month is the current (proforma) one."
          },
          "classified_pct": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Share of the month’s costo + gasto outflow that has an account, floored to one decimal (99.96 reads 99.9). 100 only when nothing is unclassified. Null when nothing went out."
          },
          "outflow_total_clp": {
            "type": "integer"
          },
          "unclassified": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "total_clp": {
                "type": "integer",
                "description": "Equals the Resultado’s «Egresos sin clasificar» for the month."
              },
              "by_kind": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "kind": {
                      "type": "string",
                      "enum": [
                        "purchase",
                        "honorario",
                        "manual_expense",
                        "movement"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "total_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "kind",
                    "count",
                    "total_clp"
                  ]
                }
              },
              "with_proposal": {
                "type": "integer",
                "description": "How many of them sit in an open, ready `classify` case: Vitrina already proposes an account."
              }
            },
            "required": [
              "count",
              "total_clp",
              "by_kind",
              "with_proposal"
            ]
          },
          "unclassified_income": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "total_clp": {
                "type": "integer"
              }
            },
            "required": [
              "count",
              "total_clp"
            ]
          },
          "findings": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "enum": [
                        "unclassified"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "total_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "key",
                    "count",
                    "total_clp"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "enum": [
                        "duplicate_import"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "pairs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "account_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "name": {
                            "type": "string"
                          },
                          "duplicates_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "duplicates_name": {
                            "type": "string"
                          },
                          "used_6m": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "account_id",
                          "name",
                          "duplicates_id",
                          "duplicates_name",
                          "used_6m"
                        ]
                      }
                    }
                  },
                  "required": [
                    "key",
                    "count",
                    "pairs"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "enum": [
                        "unused"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    },
                    "account_ids": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      }
                    }
                  },
                  "required": [
                    "key",
                    "count",
                    "account_ids"
                  ]
                }
              ]
            }
          }
        },
        "required": [
          "month",
          "in_progress",
          "classified_pct",
          "outflow_total_clp",
          "unclassified",
          "unclassified_income",
          "findings"
        ]
      },
      "FinanzasAccountReviewDismissBody": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "duplicate_import"
            ]
          },
          "account_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "key",
          "account_id"
        ],
        "additionalProperties": false
      },
      "FinanzasAccountCreateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "kind": {
            "$ref": "#/components/schemas/FinanzasAccountKind"
          },
          "behavior": {
            "$ref": "#/components/schemas/FinanzasAccountBehavior"
          },
          "no_document": {
            "type": "boolean"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "required": [
          "name"
        ],
        "additionalProperties": false
      },
      "FinanzasAccountUpdateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "behavior": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasAccountBehavior"
              },
              {
                "type": "string"
              }
            ]
          },
          "no_document": {
            "type": "boolean"
          },
          "parent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "position": {
            "type": "integer",
            "minimum": 0,
            "maximum": 1000000
          }
        },
        "additionalProperties": false
      },
      "FinanzasConnection": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "bank",
              "mercado_pago",
              "sii"
            ]
          },
          "provider": {
            "type": "string",
            "enum": [
              "fintoc",
              "bci_api_market",
              "mercadopago",
              "sii"
            ]
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "holder_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "number_masked": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "cuenta_corriente",
                    "cuenta_vista",
                    "cuenta_ahorro",
                    "tarjeta_credito",
                    "linea_credito",
                    "mercado_pago",
                    "otra"
                  ]
                },
                "currency": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "number_masked",
                "type",
                "currency"
              ]
            }
          },
          "movement_count": {
            "type": "integer"
          },
          "connected_at": {
            "type": "string"
          },
          "last_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_successful_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "first_sync",
              "pending",
              "stale",
              "error",
              "login_required",
              "disconnected"
            ],
            "description": "`first_sync`: connected, first read not landed yet (a (re)connect resets it; an empty first read stays `first_sync` for 24 h before it counts as a confirmed empty window). Show «Primera lectura en curso», never «Al día». `pending` is the old name of `first_sync`: never sent any more, kept so existing clients still validate."
          },
          "next_refresh_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "first_read_done_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "SII only: the first successful read of documents; null otherwise."
          },
          "backfill_done_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "SII only: every month of the first-sync window was read once; null otherwise."
          },
          "emission_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "certified",
              "in_certification",
              "not_needed"
            ],
            "description": "SII only: the emission step, apart from reading. `in_certification` while the certificate / mandato step is pending; `not_needed` when no emission was set up; null for banks and Mercado Pago."
          },
          "actions": {
            "type": "object",
            "properties": {
              "sync_now": {
                "type": "boolean"
              },
              "reconnect": {
                "type": "boolean"
              },
              "disconnect": {
                "type": "boolean"
              }
            },
            "required": [
              "sync_now",
              "reconnect",
              "disconnect"
            ]
          },
          "account_matches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FintocAccountMatch"
            },
            "description": "Connect and reconnect answers only: per linked account, whether it was kept, adopted the existing account with the same number and currency, or created (with the reason a match was not adopted)."
          }
        },
        "required": [
          "id",
          "kind",
          "provider",
          "institution_name",
          "holder_name",
          "accounts",
          "movement_count",
          "connected_at",
          "last_sync_at",
          "last_successful_sync_at",
          "last_sync_error",
          "status",
          "next_refresh_at",
          "first_read_done_at",
          "backfill_done_at",
          "emission_status",
          "actions"
        ]
      },
      "FinanzasConnections": {
        "type": "object",
        "properties": {
          "connections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasConnection"
            }
          },
          "unconnected_sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "kind": {
                  "type": "string",
                  "enum": [
                    "mercado_pago"
                  ]
                },
                "institution_name": {
                  "type": "string"
                },
                "data_until": {
                  "type": "string",
                  "description": "Santiago day (YYYY-MM-DD) of the newest imported movement."
                },
                "movement_count": {
                  "type": "integer"
                }
              },
              "required": [
                "kind",
                "institution_name",
                "data_until",
                "movement_count"
              ]
            },
            "description": "Sources with imported data but no live connection (ODM Mercado Pago): «Sin conexión · datos hasta el {día}»."
          },
          "fintoc": {
            "type": "object",
            "properties": {
              "configured": {
                "type": "boolean"
              },
              "entitled": {
                "type": "boolean"
              },
              "feature": {
                "type": "string",
                "enum": [
                  "bank_feed_fintoc"
                ]
              },
              "price_clp_per_bank_month": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "widget_public_key": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "configured",
              "entitled",
              "feature",
              "price_clp_per_bank_month",
              "widget_public_key"
            ]
          },
          "stale_after_hours": {
            "type": "integer"
          },
          "manual_sync_min_interval_minutes": {
            "type": "integer"
          }
        },
        "required": [
          "connections",
          "unconnected_sources",
          "fintoc",
          "stale_after_hours",
          "manual_sync_min_interval_minutes"
        ]
      },
      "FinanzasFintocLinkIntent": {
        "type": "object",
        "properties": {
          "widget_token": {
            "type": "string"
          },
          "external_id": {
            "type": "string"
          }
        },
        "required": [
          "widget_token",
          "external_id"
        ]
      },
      "FinanzasFintocConnectBody": {
        "type": "object",
        "properties": {
          "exchange_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          },
          "link_token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 512
          }
        }
      },
      "FinanzasBankPullLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_successful_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "queued": {
            "type": "boolean"
          },
          "next_refresh_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "skipped": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "login_required",
              "disconnected"
            ]
          }
        },
        "required": [
          "id",
          "institution_name",
          "last_sync_at",
          "last_successful_sync_at",
          "queued",
          "next_refresh_at",
          "skipped"
        ]
      },
      "FinanzasBankPull": {
        "type": "object",
        "properties": {
          "last_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_successful_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "queued": {
            "type": "boolean"
          },
          "enqueued": {
            "type": "integer"
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasBankPullLink"
            }
          },
          "min_interval_minutes": {
            "type": "integer"
          }
        },
        "required": [
          "last_sync_at",
          "last_successful_sync_at",
          "queued",
          "enqueued",
          "links",
          "min_interval_minutes"
        ]
      },
      "FinanzasSyncResult": {
        "type": "object",
        "properties": {
          "enqueued": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "next_refresh_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "enqueued",
          "next_refresh_at"
        ]
      },
      "FinanzasHowtoScreen": {
        "type": "string",
        "enum": [
          "resumen",
          "por_resolver",
          "hecho_por_vitrina",
          "movimientos",
          "documentos",
          "contrapartes",
          "resultado",
          "flujo_de_caja",
          "costos_y_equilibrio",
          "conexiones",
          "plan_de_cuentas",
          "reglas"
        ]
      },
      "FinanzasHowtoVideo": {
        "type": "object",
        "properties": {
          "screen": {
            "$ref": "#/components/schemas/FinanzasHowtoScreen"
          },
          "url": {
            "type": "string"
          },
          "embed_url": {
            "type": "string"
          }
        },
        "required": [
          "screen",
          "url",
          "embed_url"
        ]
      },
      "FinanzasHowtoVideos": {
        "type": "object",
        "properties": {
          "videos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasHowtoVideo"
            }
          }
        },
        "required": [
          "videos"
        ]
      },
      "FinanzasHowtoAdminRow": {
        "type": "object",
        "properties": {
          "screen": {
            "$ref": "#/components/schemas/FinanzasHowtoScreen"
          },
          "label": {
            "type": "string"
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          },
          "embed_url": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "screen",
          "label",
          "url",
          "embed_url"
        ]
      },
      "FinanzasHowtoAdminView": {
        "type": "object",
        "properties": {
          "screens": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasHowtoAdminRow"
            }
          },
          "allowed_hosts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "screens",
          "allowed_hosts"
        ]
      },
      "FinanzasHowtoSetBody": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048
          }
        },
        "required": [
          "url"
        ]
      },
      "FinanzasClaveTributaria": {
        "type": "object",
        "properties": {
          "connection_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "activa",
              "verificando",
              "requiere_actualizar",
              "no_configurada"
            ],
            "description": "activa | verificando | requiere_actualizar (the SII rejected the stored clave: sync paused until it is updated) | no_configurada."
          },
          "status_changed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "pending_replacement": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "since"
            ],
            "description": "A new clave is being verified; the previous one keeps working until it is."
          },
          "last_verification": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "outcome": {
                "type": "string",
                "enum": [
                  "verified",
                  "rejected",
                  "deferred"
                ]
              },
              "at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "outcome",
              "at"
            ]
          },
          "consent": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version": {
                "type": "string"
              },
              "accepted_at": {
                "type": "string"
              },
              "current": {
                "type": "boolean"
              }
            },
            "required": [
              "version",
              "accepted_at",
              "current"
            ]
          },
          "consent_required": {
            "type": "boolean",
            "description": "True when the consent screen must be shown before the clave (none on record, or an older text version)."
          },
          "consent_text": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "points": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "authorization": {
                "type": "string"
              }
            },
            "required": [
              "version",
              "title",
              "points",
              "authorization"
            ]
          },
          "live": {
            "type": "boolean",
            "description": "False when the SII service could not be asked: the last known state."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "actions": {
            "type": "object",
            "properties": {
              "connect": {
                "type": "boolean"
              },
              "update": {
                "type": "boolean"
              },
              "disconnect": {
                "type": "boolean"
              }
            },
            "required": [
              "connect",
              "update",
              "disconnect"
            ]
          }
        },
        "required": [
          "connection_id",
          "status",
          "status_changed_at",
          "verified_at",
          "last_used_at",
          "pending_replacement",
          "last_verification",
          "consent",
          "consent_required",
          "consent_text",
          "live",
          "checked_at",
          "actions"
        ]
      },
      "FinanzasContraparte": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "rut": {
            "type": [
              "string",
              "null"
            ],
            "description": "Canonical form `76123456-0`; null = keyed by name."
          },
          "rut_formatted": {
            "type": [
              "string",
              "null"
            ],
            "example": "76.123.456-0"
          },
          "roles": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "supplier",
                "customer"
              ]
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "movement",
              "document",
              "manual",
              "import"
            ]
          },
          "contact": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "clinic_patient": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "is_payroll": {
            "type": "boolean",
            "description": "A person on payroll. Without `finanzas_payroll:read` («Ver remuneraciones») the name reads «Persona (remuneraciones)» and the RUT is null."
          },
          "movement_count": {
            "type": "integer"
          },
          "document_count": {
            "type": "integer"
          },
          "merged_into_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Non-null = merged away; use the survivor."
          },
          "payment_terms_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Its own payment terms in days (0–365); null = the workspace default of each document class (Ajustes › Plazos de pago)."
          },
          "needs_name": {
            "type": "boolean",
            "description": "True while the name is only the RUT (no document has told us the razón social yet): show «Sin nombre aún» + the RUT, never the RUT as a title."
          },
          "payment_terms_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "chipax",
              "manual"
            ],
            "description": "Where `payment_terms_days` came from: chipax (the importer read it) or manual (a person set it); null when it has no terms of its own."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "rut",
          "rut_formatted",
          "roles",
          "source",
          "contact",
          "clinic_patient",
          "is_payroll",
          "movement_count",
          "document_count",
          "merged_into_id",
          "payment_terms_days",
          "needs_name",
          "payment_terms_source",
          "created_at",
          "updated_at"
        ]
      },
      "FinanzasContraparteUpdateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "clinic_patient_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        }
      },
      "FinanzasContraparteMergeResult": {
        "type": "object",
        "properties": {
          "survivor": {
            "$ref": "#/components/schemas/FinanzasContraparte"
          },
          "merged_id": {
            "type": "string"
          },
          "merge_id": {
            "type": "string",
            "description": "The merge ledger id: `POST /finanzas/contrapartes/merges/{mergeId}/undo` undoes it."
          },
          "moved": {
            "type": "object",
            "properties": {
              "movements": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              }
            },
            "required": [
              "movements",
              "documents"
            ]
          }
        },
        "required": [
          "survivor",
          "merged_id",
          "merge_id",
          "moved"
        ]
      },
      "FinanzasContraparteMergeBody": {
        "type": "object",
        "properties": {
          "merge_id": {
            "type": "string",
            "format": "uuid",
            "description": "The duplicate that is merged into `{id}`."
          }
        },
        "required": [
          "merge_id"
        ]
      },
      "FinanzasContraparteDuplicatePair": {
        "type": "object",
        "properties": {
          "keep": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut_formatted": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "movement_count": {
                "type": "integer"
              },
              "document_count": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "name",
              "rut",
              "rut_formatted",
              "movement_count",
              "document_count"
            ],
            "description": "The one that stays."
          },
          "merge": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut_formatted": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "movement_count": {
                "type": "integer"
              },
              "document_count": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "name",
              "rut",
              "rut_formatted",
              "movement_count",
              "document_count"
            ],
            "description": "The one that is merged into `keep`."
          },
          "score": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "moves": {
            "type": "object",
            "properties": {
              "movements": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              }
            },
            "required": [
              "movements",
              "documents"
            ],
            "description": "What moves to `keep`."
          }
        },
        "required": [
          "keep",
          "merge",
          "score",
          "moves"
        ]
      },
      "FinanzasContraparteDuplicates": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "description": "Every proposed pair; `items` holds the first 200."
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasContraparteDuplicatePair"
            }
          }
        },
        "required": [
          "total",
          "items"
        ]
      },
      "FinanzasContraparteDuplicateMergeBody": {
        "type": "object",
        "properties": {
          "keep_id": {
            "type": "string",
            "format": "uuid"
          },
          "merge_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "keep_id",
          "merge_id"
        ]
      },
      "FinanzasContraparteDuplicateDismissBody": {
        "type": "object",
        "properties": {
          "a_id": {
            "type": "string",
            "format": "uuid"
          },
          "b_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "a_id",
          "b_id"
        ]
      },
      "FinanzasContraparteMergeView": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "origin": {
            "type": "string",
            "enum": [
              "same_rut",
              "same_name",
              "review",
              "manual"
            ]
          },
          "actor": {
            "type": "string",
            "enum": [
              "system",
              "person"
            ]
          },
          "label": {
            "type": "string",
            "example": "Adolfo Cerda A → ADOLFO CERDA ALVAREZ"
          },
          "survivor": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "merged": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "moved": {
            "type": "object",
            "properties": {
              "movements": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              }
            },
            "required": [
              "movements",
              "documents"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "undone_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "undoable": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "origin",
          "actor",
          "label",
          "survivor",
          "merged",
          "moved",
          "reason",
          "created_at",
          "undone_at",
          "undoable"
        ]
      },
      "FinanzasContraparteUndoMergeResult": {
        "type": "object",
        "properties": {
          "merge_id": {
            "type": "string",
            "format": "uuid"
          },
          "survivor": {
            "$ref": "#/components/schemas/FinanzasContraparte"
          },
          "restored": {
            "$ref": "#/components/schemas/FinanzasContraparte"
          },
          "moved_back": {
            "type": "object",
            "properties": {
              "movements": {
                "type": "integer"
              },
              "documents": {
                "type": "integer"
              }
            },
            "required": [
              "movements",
              "documents"
            ]
          }
        },
        "required": [
          "merge_id",
          "survivor",
          "restored",
          "moved_back"
        ]
      },
      "FinanzasContraparteTerms": {
        "type": "object",
        "properties": {
          "days": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 365,
            "description": "Its own terms; null = the workspace default per class."
          },
          "by_class": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "class": {
                  "type": "string",
                  "enum": [
                    "suppliers",
                    "customers",
                    "honorarios",
                    "expenses"
                  ],
                  "description": "Document class: suppliers = compras (proveedores), customers = ventas (clientes), honorarios = boletas de honorarios, expenses = gastos."
                },
                "label": {
                  "type": "string"
                },
                "days": {
                  "type": "integer"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "contraparte",
                    "workspace",
                    "default"
                  ]
                },
                "origin": {
                  "type": "string",
                  "enum": [
                    "chipax",
                    "default",
                    "manual"
                  ],
                  "description": "Where the number comes from: chipax (read from the Chipax company by the importer), manual (set by a person in Ajustes or on the Contraparte), default (the built-in 30 / 30 / 0 / 0)."
                }
              },
              "required": [
                "class",
                "label",
                "days",
                "source",
                "origin"
              ]
            }
          }
        },
        "required": [
          "days",
          "by_class"
        ]
      },
      "FinanzasContraparteAlias": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "rut",
              "bank_account",
              "descriptor"
            ],
            "description": "`descriptor`: a card / automatic-debit descriptor (a bank line with no RUT or account) learned when a person confirmed such a movement in «Por resolver» against this Contraparte's documents. Removed like any alias (`DELETE …/aliases/{aliasId}`); never added by hand."
          },
          "display": {
            "type": "string",
            "description": "A RUT formatted (`16.123.456-7`); an account masked; a descriptor folded (`MERPAGO*MERCADOL`)."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "display",
          "created_at"
        ]
      },
      "FinanzasAccountRef": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "ingreso",
              "costo",
              "gasto",
              "no_operacional"
            ]
          },
          "archived": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "name",
          "path",
          "kind",
          "archived"
        ]
      },
      "FinanzasRule": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": "string",
            "description": "The rule as one Spanish sentence."
          },
          "target": {
            "type": "string",
            "enum": [
              "document",
              "movement"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "rut"
            ]
          },
          "text_contains": {
            "type": [
              "string",
              "null"
            ]
          },
          "amount_min_clp": {
            "type": [
              "integer",
              "null"
            ]
          },
          "amount_max_clp": {
            "type": [
              "integer",
              "null"
            ]
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "document_kind": {
            "type": [
              "string",
              "null"
            ]
          },
          "account": {
            "$ref": "#/components/schemas/FinanzasAccountRef"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "archived"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "person",
              "vitrina"
            ],
            "description": "person = created by a person; vitrina = Vitrina created it, after a person confirmed the same classification twice for this Contraparte or from a consistent history (≥3 items, ≥90 % on one account), announced in «Hecho por Vitrina». Archive it to say no: Vitrina never proposes it again."
          },
          "accrual": {
            "type": "string",
            "enum": [
              "issue_month",
              "previous_month"
            ],
            "description": "The month the rule books a document in: issue_month, or previous_month («mes anterior»)."
          },
          "accrual_set_by": {
            "type": "string",
            "enum": [
              "default",
              "vitrina",
              "person"
            ],
            "description": "Who chose `accrual`: default, vitrina (learned from where this supplier's documents were booked before) or person."
          },
          "run_count": {
            "type": "integer"
          },
          "last_applied_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "label",
          "target",
          "contraparte",
          "text_contains",
          "amount_min_clp",
          "amount_max_clp",
          "direction",
          "document_kind",
          "account",
          "status",
          "origin",
          "accrual",
          "accrual_set_by",
          "run_count",
          "last_applied_at",
          "created_at",
          "updated_at"
        ]
      },
      "FinanzasContrapartePage": {
        "type": "object",
        "properties": {
          "contraparte": {
            "$ref": "#/components/schemas/FinanzasContraparte"
          },
          "balance": {
            "type": "object",
            "properties": {
              "payable": {
                "type": "object",
                "properties": {
                  "open_clp": {
                    "type": "integer"
                  },
                  "overdue_clp": {
                    "type": "integer"
                  },
                  "open_count": {
                    "type": "integer"
                  },
                  "overdue_count": {
                    "type": "integer"
                  },
                  "oldest_overdue_due_date": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "next_due_date": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "open_clp",
                  "overdue_clp",
                  "open_count",
                  "overdue_count",
                  "oldest_overdue_due_date",
                  "next_due_date"
                ],
                "description": "What the workspace owes it: open purchases, honorarios and gastos."
              },
              "receivable": {
                "type": "object",
                "properties": {
                  "open_clp": {
                    "type": "integer"
                  },
                  "overdue_clp": {
                    "type": "integer"
                  },
                  "open_count": {
                    "type": "integer"
                  },
                  "overdue_count": {
                    "type": "integer"
                  },
                  "oldest_overdue_due_date": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "next_due_date": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "open_clp",
                  "overdue_clp",
                  "open_count",
                  "overdue_count",
                  "oldest_overdue_due_date",
                  "next_due_date"
                ],
                "description": "What it owes the workspace: open sales and issued boletas de honorarios (their líquido)."
              }
            },
            "required": [
              "payable",
              "receivable"
            ]
          },
          "terms": {
            "$ref": "#/components/schemas/FinanzasContraparteTerms"
          },
          "aliases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasContraparteAlias"
            }
          },
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasRule"
            }
          },
          "open_case": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "id"
            ],
            "description": "Its open «Por resolver» case (the biggest at stake when it has several), to link to `GET /finanzas/cases/{id}`; null when none."
          },
          "as_of": {
            "type": "string",
            "description": "Today (America/Santiago)."
          }
        },
        "required": [
          "contraparte",
          "balance",
          "terms",
          "aliases",
          "rules",
          "open_case",
          "as_of"
        ]
      },
      "FinanzasMovement": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "example": "2026-10-29"
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "amount_clp": {
            "type": "integer",
            "description": "Whole CLP, > 0 (the CLP equivalent for USD)."
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP",
              "USD"
            ]
          },
          "original": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "currency": {
                "type": "string",
                "enum": [
                  "USD"
                ]
              },
              "amount_minor": {
                "type": "integer",
                "description": "Cents."
              },
              "fx_rate_clp": {
                "type": "number",
                "description": "CLP per 1 USD."
              },
              "fx_rate_date": {
                "type": "string"
              }
            },
            "required": [
              "currency",
              "amount_minor",
              "fx_rate_clp",
              "fx_rate_date"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_rut_formatted": {
            "type": [
              "string",
              "null"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "rut_formatted": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "rut_formatted"
            ]
          },
          "bank_account": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "institution_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "number_masked": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "currency": {
                "type": "string",
                "enum": [
                  "CLP",
                  "USD"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "institution_name",
              "number_masked",
              "currency"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "partial",
              "reconciled"
            ]
          },
          "assigned_clp": {
            "type": "integer"
          },
          "pending_clp": {
            "type": "integer"
          },
          "resolved_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "rule",
              "match",
              "ai",
              "person"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "bank_feed",
              "email",
              "mercado_pago",
              "cartola"
            ]
          },
          "operation_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "payer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "name",
              "email"
            ],
            "description": "Who paid, as a payment processor reports it (Mercado Pago: name and e-mail). Null for bank movements."
          },
          "comment_count": {
            "type": "integer"
          },
          "ads_document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Finanzas 30: the ad document (`GET /finanzas/documents/{id}`, `origin = ads`) of this movement when it is an ad charge or refund; null otherwise."
          },
          "card_processor": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "key": {
                "type": "string",
                "enum": [
                  "pagos_y_servicios",
                  "transbank",
                  "getnet",
                  "mercado_pago",
                  "healthatom"
                ]
              },
              "name": {
                "type": "string"
              },
              "rut_formatted": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "key",
              "name",
              "rut_formatted"
            ],
            "description": "Finanzas 36: set when this is a known card processor's payout («Cobro con tarjeta»: PNOL / PAGOS Y SERVICIOS, Transbank, Getnet, Mercado Pago). It settles the month's boleta summary and never adds income. Null for any other movement and for money out."
          },
          "generic_label": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "Depósito en efectivo",
              "Pago recibido",
              "Depósito con cheque",
              "Pago de tarjeta de crédito"
            ],
            "description": "Finanzas 36: set when the bank text names nobody and the movement has no Contraparte: show it as the title with the sub-line «Sin contraparte»."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "date",
          "direction",
          "amount_clp",
          "currency",
          "original",
          "description",
          "counterparty_name",
          "counterparty_rut_formatted",
          "contraparte",
          "bank_account",
          "state",
          "assigned_clp",
          "pending_clp",
          "resolved_by",
          "source",
          "operation_id",
          "payer",
          "comment_count",
          "ads_document_id",
          "card_processor",
          "generic_label",
          "created_at"
        ]
      },
      "FinanzasDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "purchase",
              "sale",
              "honorario",
              "manual_expense",
              "tax"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "sii",
              "vitrina",
              "manual",
              "chipax"
            ]
          },
          "dte_type": {
            "type": [
              "integer",
              "null"
            ]
          },
          "dte_type_label": {
            "type": [
              "string",
              "null"
            ],
            "example": "Factura electrónica"
          },
          "is_credit_note": {
            "type": "boolean",
            "description": "DTE 61: the libro subtracts it."
          },
          "folio": {
            "type": [
              "string",
              "null"
            ]
          },
          "issue_date": {
            "type": "string",
            "example": "2026-10-03"
          },
          "period": {
            "type": "string",
            "example": "2026-10"
          },
          "received_on": {
            "type": [
              "string",
              "null"
            ]
          },
          "issuer_rut_formatted": {
            "type": [
              "string",
              "null"
            ]
          },
          "receiver_rut_formatted": {
            "type": [
              "string",
              "null"
            ]
          },
          "counterparty_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The other side, as the document names it."
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "rut_formatted": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "rut_formatted"
            ]
          },
          "amounts": {
            "type": "object",
            "properties": {
              "net": {
                "type": "integer"
              },
              "exempt": {
                "type": "integer"
              },
              "iva": {
                "type": "integer"
              },
              "other_taxes": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "net",
              "exempt",
              "iva",
              "other_taxes",
              "total"
            ],
            "description": "Whole CLP as the document prints them — positive also on a nota de crédito."
          },
          "due_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "payment_state": {
            "type": "string",
            "enum": [
              "por_pagar",
              "parcial",
              "pagado",
              "vencido_heredado"
            ],
            "description": "por_pagar (a sale: por cobrar) | parcial | pagado. Derived from reconciliation; a document settled without a bank link (`settlement`) is pagado. `vencido_heredado` is retired (Finanzas 50): nothing writes it; only rows written before it, outside that backfill, still carry it, and they count like a paid document."
          },
          "settlement": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "person",
                  "paid_before_vitrina"
                ],
                "description": "paid_before_vitrina = «Pagado antes de Vitrina»: from a period closed before Vitrina, still open there, so treated as settled (out of the cash projection and the balances). person = someone marked it pagado («Marcar pagada»)."
              },
              "batch_id": {
                "type": "string",
                "format": "uuid"
              },
              "settled_at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "reason",
              "batch_id",
              "settled_at"
            ],
            "description": "Settled without a bank link (Finanzas 50); null when the state comes from reconciliation alone. POST /finanzas/documents/{id}/reopen undoes it."
          },
          "sii_estado_contable": {
            "type": [
              "string",
              "null"
            ],
            "description": "REGISTRO | PENDIENTE | NO_INCLUIR | RECLAMADO (purchases)."
          },
          "missing_at_sii": {
            "type": "boolean",
            "description": "The SII register no longer lists it; out of the libro."
          },
          "voided": {
            "type": "boolean"
          },
          "adjusts": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "dte_type_label": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "dte_type",
              "dte_type_label",
              "folio"
            ],
            "description": "A nota de crédito / débito: the document it adjusts."
          },
          "f29": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "period": {
                "type": "string",
                "example": "2026-09"
              },
              "state": {
                "type": "string",
                "enum": [
                  "proposal",
                  "in_progress",
                  "filed",
                  "rejected"
                ],
                "description": "proposal = computed, not filed; in_progress = submitted, the SII has not answered; filed = accepted by the SII; rejected = the latest filing was rejected."
              },
              "state_label": {
                "type": "string",
                "example": "Declarado"
              },
              "filed": {
                "type": "boolean"
              },
              "amount_is_final": {
                "type": "boolean",
                "description": "false while the amount is the proposal, not a filed one."
              },
              "filed_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "filing_folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rectifications": {
                "type": "integer",
                "description": "Accepted filings of the period beyond the first."
              },
              "summary": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "iva_debito": {
                    "type": "integer"
                  },
                  "iva_credito": {
                    "type": "integer"
                  },
                  "iva_retenido": {
                    "type": "integer"
                  },
                  "remanente_anterior": {
                    "type": "integer"
                  },
                  "remanente_siguiente": {
                    "type": "integer"
                  },
                  "ppm": {
                    "type": "integer"
                  }
                },
                "required": [
                  "iva_debito",
                  "iva_credito",
                  "iva_retenido",
                  "remanente_anterior",
                  "remanente_siguiente",
                  "ppm"
                ]
              }
            },
            "required": [
              "period",
              "state",
              "state_label",
              "filed",
              "amount_is_final",
              "filed_at",
              "filing_folio",
              "rectifications",
              "summary"
            ],
            "description": "F29 only (`kind = tax`); null on every other kind. The amount to pay is `amounts.total`, the due date `due_date`, paid or not `payment_state`."
          },
          "honorario": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "direction": {
                "type": "string",
                "enum": [
                  "received",
                  "issued"
                ]
              },
              "gross": {
                "type": "integer",
                "description": "«Total honorarios», whole CLP (= amounts.total)."
              },
              "retention": {
                "type": "integer",
                "description": "The retención (F29 código 151 when received)."
              },
              "net": {
                "type": "integer",
                "description": "Líquido: gross − retención. What is paid, and what reconciliation settles."
              },
              "state": {
                "type": "string",
                "enum": [
                  "vigente",
                  "anulada"
                ]
              }
            },
            "required": [
              "direction",
              "gross",
              "retention",
              "net",
              "state"
            ],
            "description": "Boletas de honorarios only (kind honorario); null on every other kind."
          },
          "ads": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "platform": {
                "type": "string",
                "enum": [
                  "meta_ads",
                  "google_ads"
                ]
              },
              "kind": {
                "type": "string",
                "enum": [
                  "charge",
                  "refund"
                ]
              },
              "movement_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "new",
                  "pending",
                  "linked",
                  "unconfirmed",
                  "no_connection"
                ],
                "description": "linked = Vitrina linked the movement (see evidence); unconfirmed / no_connection = waiting in «Por resolver»; pending = the ad data could not be read yet; new = not decided."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ad_account_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "customer_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Meta: the card statement reference (`FACEBK *<ref>`)."
              },
              "meta_transaction_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Meta's «Transaction ID» of the charge, once linked."
              },
              "evidence": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "spend_window": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "ad_account_id": {
                    "type": "string"
                  },
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  },
                  "spend_minor": {
                    "type": "integer"
                  },
                  "currency": {
                    "type": "string"
                  }
                },
                "required": [
                  "ad_account_id",
                  "from",
                  "to",
                  "spend_minor",
                  "currency"
                ]
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The card movement's original currency."
              },
              "amount_original": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "platform",
              "kind",
              "movement_id",
              "verdict",
              "reason",
              "ad_account_id",
              "customer_id",
              "reference",
              "meta_transaction_id",
              "evidence",
              "spend_window",
              "currency",
              "amount_original"
            ],
            "description": "Finanzas 30: a document Vitrina made from a Meta / Google card charge (`origin = ads`); null on every other document."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "kind",
          "source",
          "dte_type",
          "dte_type_label",
          "is_credit_note",
          "folio",
          "issue_date",
          "period",
          "received_on",
          "issuer_rut_formatted",
          "receiver_rut_formatted",
          "counterparty_name",
          "contraparte",
          "amounts",
          "due_date",
          "payment_state",
          "settlement",
          "sii_estado_contable",
          "missing_at_sii",
          "voided",
          "adjusts",
          "f29",
          "honorario",
          "created_at",
          "updated_at"
        ]
      },
      "FinanzasContraparteTimelineItem": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "document",
              "movement"
            ]
          },
          "date": {
            "type": "string"
          },
          "movement": {
            "$ref": "#/components/schemas/FinanzasMovement"
          },
          "document": {
            "$ref": "#/components/schemas/FinanzasDocument"
          },
          "paid_clp": {
            "type": "integer"
          },
          "outstanding_clp": {
            "type": "integer"
          },
          "overdue": {
            "type": "boolean"
          }
        },
        "required": [
          "type",
          "date"
        ]
      },
      "FinanzasContraparteTermsResult": {
        "type": "object",
        "properties": {
          "terms": {
            "$ref": "#/components/schemas/FinanzasContraparteTerms"
          },
          "recomputed_documents": {
            "type": "integer"
          }
        },
        "required": [
          "terms",
          "recomputed_documents"
        ]
      },
      "FinanzasContraparteTermsBody": {
        "type": "object",
        "properties": {
          "days": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 365,
            "description": "Payment terms in days; null = back to the defaults."
          }
        },
        "required": [
          "days"
        ]
      },
      "FinanzasContraparteAliasChange": {
        "type": "object",
        "properties": {
          "alias": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasContraparteAlias"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "aliases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasContraparteAlias"
            }
          },
          "reconciled": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "documents": {
                "type": "integer"
              },
              "transfers": {
                "type": "integer"
              },
              "cash_deposits": {
                "type": "integer"
              },
              "payments": {
                "type": "integer"
              },
              "expenses": {
                "type": "integer"
              }
            },
            "required": [
              "documents",
              "transfers",
              "cash_deposits",
              "payments",
              "expenses"
            ],
            "description": "What the reconciliation engine linked right after (last 60 days)."
          }
        },
        "required": [
          "alias",
          "aliases",
          "reconciled"
        ]
      },
      "FinanzasContraparteAliasBody": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "rut",
              "bank_account"
            ]
          },
          "value": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "description": "A RUT in any punctuation, or a bank account number (digits; dashes and leading zeros are ignored)."
          }
        },
        "required": [
          "kind",
          "value"
        ]
      },
      "FinanzasContraparteAliasRemoved": {
        "type": "object",
        "properties": {
          "removed": {
            "$ref": "#/components/schemas/FinanzasContraparteAlias"
          },
          "aliases": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasContraparteAlias"
            }
          }
        },
        "required": [
          "removed",
          "aliases"
        ]
      },
      "FinanzasPaymentTermsDefault": {
        "type": "object",
        "properties": {
          "class": {
            "type": "string",
            "enum": [
              "suppliers",
              "customers",
              "honorarios",
              "expenses"
            ],
            "description": "Document class: suppliers = compras (proveedores), customers = ventas (clientes), honorarios = boletas de honorarios, expenses = gastos."
          },
          "label": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "customized": {
            "type": "boolean"
          },
          "source": {
            "type": "string",
            "enum": [
              "chipax",
              "default",
              "manual"
            ],
            "description": "Where the number comes from: chipax (read from the Chipax company by the importer), manual (set by a person in Ajustes or on the Contraparte), default (the built-in 30 / 30 / 0 / 0)."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "class",
          "label",
          "days",
          "customized",
          "source",
          "updated_at"
        ]
      },
      "FinanzasPaymentTermsResult": {
        "type": "object",
        "properties": {
          "defaults": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasPaymentTermsDefault"
            }
          },
          "recomputed_documents": {
            "type": "integer"
          }
        },
        "required": [
          "defaults",
          "recomputed_documents"
        ]
      },
      "FinanzasPaymentTermsBody": {
        "type": "object",
        "properties": {
          "suppliers": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "customers": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "honorarios": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          },
          "expenses": {
            "type": "integer",
            "minimum": 0,
            "maximum": 365
          }
        },
        "additionalProperties": false
      },
      "FinanzasCashPendingSummary": {
        "type": "object",
        "properties": {
          "amount_clp": {
            "type": "integer",
            "description": "Whole CLP of cash still to deposit (the standing list of the last `window_days` days up to yesterday, minus what deposits already cover)."
          },
          "window_days": {
            "type": "integer",
            "description": "The window the amount is read over (60)."
          },
          "expected_until": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last day the Flujo expects the deposits (5 business days from today); null when nothing is pending."
          }
        },
        "required": [
          "amount_clp",
          "window_days",
          "expected_until"
        ]
      },
      "FinanzasMovementComment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "author": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "user_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "user_id",
              "name"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "body",
          "author",
          "created_at"
        ]
      },
      "FinanzasMovementDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FinanzasMovement"
          },
          {
            "type": "object",
            "properties": {
              "comments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FinanzasMovementComment"
                }
              }
            },
            "required": [
              "comments"
            ]
          }
        ]
      },
      "FinanzasMovementCommentBody": {
        "type": "object",
        "properties": {
          "body": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          }
        },
        "required": [
          "body"
        ]
      },
      "FinanzasBankAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "number_masked": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP",
              "USD"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "fintoc",
              "mercado_pago",
              "manual"
            ]
          },
          "has_feed": {
            "type": "boolean"
          },
          "balance_clp": {
            "type": [
              "integer",
              "null"
            ]
          },
          "balance_original": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "currency": {
                "type": "string",
                "enum": [
                  "USD"
                ]
              },
              "amount_minor": {
                "type": "integer",
                "description": "Cents."
              },
              "fx_rate_clp": {
                "type": "number",
                "description": "CLP per 1 USD."
              },
              "fx_rate_date": {
                "type": "string"
              }
            },
            "required": [
              "currency",
              "amount_minor",
              "fx_rate_clp",
              "fx_rate_date"
            ]
          },
          "balance_as_of": {
            "type": [
              "string",
              "null"
            ]
          },
          "balance_source": {
            "type": "string",
            "enum": [
              "feed",
              "computed",
              "unknown"
            ]
          },
          "opening_balance": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount_minor": {
                "type": "integer"
              },
              "on": {
                "type": "string"
              }
            },
            "required": [
              "amount_minor",
              "on"
            ]
          },
          "opening_balance_editable": {
            "type": "boolean"
          },
          "movement_count": {
            "type": "integer"
          },
          "data_through": {
            "type": [
              "string",
              "null"
            ],
            "description": "Date of the account's newest movement (YYYY-MM-DD)."
          },
          "stale": {
            "type": "boolean",
            "description": "true = no connection feeds the account and its newest movement is older than 37 days: its cartola is late (warning, never sent as an email or WhatsApp)."
          },
          "agent_sharing": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "shared": {
                "type": "boolean",
                "description": "true = the AI agent dictates this account for transfers and abonos into it are matched; false = it stopped sharing it."
              },
              "alias": {
                "type": "string"
              },
              "holder_name": {
                "type": "string"
              },
              "holder_rut": {
                "type": "string"
              },
              "holder_email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "account_type": {
                "type": "string",
                "enum": [
                  "corriente",
                  "vista",
                  "ahorro"
                ]
              },
              "holder_not_company": {
                "type": "boolean",
                "description": "true = the holder RUT differs from the company RUT (the workspace's SII RUTs): the account card shows «El titular no es la empresa». Always false when the company RUT is not known."
              }
            },
            "required": [
              "shared",
              "alias",
              "holder_name",
              "holder_rut",
              "holder_email",
              "account_type",
              "holder_not_company"
            ],
            "description": "Finanzas 33: «el agente comparte esta cuenta», with the holder details the agent dictates. Null = never shared."
          },
          "can_share_with_agent": {
            "type": "boolean",
            "description": "Whether the agent can share it: a CLP corriente / vista / ahorro / Mercado Pago account with its full number (not a card, a credit line, a USD account or a last-4 number)."
          }
        },
        "required": [
          "id",
          "name",
          "institution_name",
          "number_masked",
          "type",
          "currency",
          "source",
          "has_feed",
          "balance_clp",
          "balance_original",
          "balance_as_of",
          "balance_source",
          "opening_balance",
          "opening_balance_editable",
          "movement_count",
          "data_through",
          "stale",
          "agent_sharing",
          "can_share_with_agent"
        ]
      },
      "FinanzasBankAccounts": {
        "type": "object",
        "properties": {
          "accounts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasBankAccount"
            }
          },
          "total_clp": {
            "type": "integer"
          },
          "as_of": {
            "type": [
              "string",
              "null"
            ]
          },
          "unknown_count": {
            "type": "integer"
          }
        },
        "required": [
          "accounts",
          "total_clp",
          "as_of",
          "unknown_count"
        ]
      },
      "FinanzasOpeningBalanceBody": {
        "type": "object",
        "properties": {
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -9007199254740991,
            "maximum": 9007199254740991,
            "description": "Balance at the END of `on`, in the account currency smallest unit (CLP pesos, USD cents). null clears it."
          },
          "on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          }
        },
        "required": [
          "amount_minor"
        ]
      },
      "FinanzasAgentSharingBody": {
        "type": "object",
        "properties": {
          "shared": {
            "type": "boolean",
            "description": "true = the AI agent shares this account for transfers (and abonos into it are matched); false = stop sharing it (the row is kept, never deleted)."
          },
          "confirm_holder_not_company": {
            "type": "boolean",
            "description": "Required (true) to turn sharing on when the holder RUT is not the company's; without it the call answers 409 `agent_sharing_holder_not_company` naming the holder. Never needed to keep an already-shared account or to stop sharing. Set it only after the human has explicitly agreed; an agent must not add it by itself to get past the 409."
          },
          "alias": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The label the agent and Ajustes show. Default «‹banco› ··‹últimos 4›»."
          },
          "holder_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Account holder as the bank shows it. Required the first time the account is shared."
          },
          "holder_rut": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20,
            "description": "Holder RUT (any formatting, check digit validated). Default: the workspace's SII RUT."
          },
          "holder_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 254,
            "format": "email"
          },
          "account_type": {
            "type": "string",
            "enum": [
              "corriente",
              "vista",
              "ahorro"
            ],
            "description": "What the agent dictates. Default from the account type (Mercado Pago = vista)."
          }
        },
        "required": [
          "shared"
        ]
      },
      "FinanzasF29Overview": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "periods": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasDocument"
            },
            "description": "F29 documents (`kind = tax`), newest period first."
          },
          "next_due": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "period": {
                "type": "string"
              },
              "due_date": {
                "type": "string"
              },
              "amount": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Whole CLP; null while the F29 amount is not known yet."
              },
              "document_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "state": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "proposal",
                  "in_progress",
                  "filed",
                  "rejected"
                ]
              }
            },
            "required": [
              "period",
              "due_date",
              "amount",
              "document_id",
              "state"
            ]
          }
        },
        "required": [
          "from",
          "to",
          "periods",
          "next_due"
        ]
      },
      "FinanzasLibro": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "purchase",
              "sale"
            ]
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "period": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                },
                "net": {
                  "type": "integer"
                },
                "exempt": {
                  "type": "integer"
                },
                "iva": {
                  "type": "integer"
                },
                "other_taxes": {
                  "type": "integer"
                },
                "total": {
                  "type": "integer"
                },
                "iva_fiscal": {
                  "type": "integer",
                  "description": "Purchases: IVA crédito fiscal (REGISTRO only). Sales: IVA débito fiscal."
                },
                "lines": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "dte_type": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "dte_type_label": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "count": {
                        "type": "integer"
                      },
                      "net": {
                        "type": "integer"
                      },
                      "exempt": {
                        "type": "integer"
                      },
                      "iva": {
                        "type": "integer"
                      },
                      "other_taxes": {
                        "type": "integer"
                      },
                      "total": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "dte_type",
                      "dte_type_label",
                      "count",
                      "net",
                      "exempt",
                      "iva",
                      "other_taxes",
                      "total"
                    ]
                  }
                }
              },
              "required": [
                "period",
                "count",
                "net",
                "exempt",
                "iva",
                "other_taxes",
                "total",
                "iva_fiscal",
                "lines"
              ]
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "net": {
                "type": "integer"
              },
              "exempt": {
                "type": "integer"
              },
              "iva": {
                "type": "integer"
              },
              "other_taxes": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "iva_fiscal": {
                "type": "integer"
              }
            },
            "required": [
              "count",
              "net",
              "exempt",
              "iva",
              "other_taxes",
              "total",
              "iva_fiscal"
            ]
          }
        },
        "required": [
          "kind",
          "from",
          "to",
          "months",
          "totals"
        ]
      },
      "FinanzasHonorariosSummary": {
        "type": "object",
        "properties": {
          "period": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP"
            ]
          },
          "received": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "gross_clp": {
                "type": "integer"
              },
              "retention_clp": {
                "type": "integer"
              },
              "net_clp": {
                "type": "integer"
              }
            },
            "required": [
              "count",
              "gross_clp",
              "retention_clp",
              "net_clp"
            ]
          },
          "issued": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "gross_clp": {
                "type": "integer"
              },
              "retention_clp": {
                "type": "integer"
              },
              "net_clp": {
                "type": "integer"
              }
            },
            "required": [
              "count",
              "gross_clp",
              "retention_clp",
              "net_clp"
            ]
          },
          "f29_codigo_151_clp": {
            "type": "integer",
            "description": "The retención on the boletas the company received in the month (F29 código 151)."
          },
          "clave_status": {
            "type": "string",
            "enum": [
              "activa",
              "verificando",
              "requiere_actualizar",
              "no_configurada"
            ],
            "description": "The SII clave status. Anything but activa: zeros mean «unknown», not «none»."
          }
        },
        "required": [
          "period",
          "currency",
          "received",
          "issued",
          "f29_codigo_151_clp",
          "clave_status"
        ]
      },
      "FinanzasDocumentFile": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_review",
              "confirmed",
              "discarded",
              "failed"
            ],
            "description": "pending_review = read, waiting for a person («Por revisar»); confirmed = it is (or is attached to) `document_id`; discarded; failed = not a readable file."
          },
          "channel": {
            "type": "string",
            "enum": [
              "upload",
              "email"
            ]
          },
          "filename": {
            "type": "string"
          },
          "mime_type": {
            "type": "string"
          },
          "byte_size": {
            "type": "integer"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Short-lived signed URL of the file (private storage); ask again after `url_expires_at`. Null once discarded."
          },
          "url_expires_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "movement_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "email": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "from": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "subject": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "from",
              "subject"
            ]
          },
          "extraction_status": {
            "type": "string",
            "enum": [
              "pending",
              "complete",
              "partial",
              "failed"
            ]
          },
          "extracted": {
            "type": "object",
            "properties": {
              "total_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "amount_original": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The printed amount when the currency is not CLP."
              },
              "fx_rate_clp": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The dólar observado a USD amount was converted at."
              },
              "issue_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issuer_rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issuer_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "total_clp",
              "currency",
              "amount_original",
              "fx_rate_clp",
              "issue_date",
              "issuer_rut",
              "issuer_name",
              "dte_type",
              "folio",
              "description"
            ],
            "description": "What Vitrina read. Every field is editable on confirm."
          },
          "missing": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "total_clp",
                "issue_date",
                "issuer"
              ]
            },
            "description": "What the reading could not find."
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "status",
          "channel",
          "filename",
          "mime_type",
          "byte_size",
          "url",
          "url_expires_at",
          "movement_id",
          "case_id",
          "email",
          "extraction_status",
          "extracted",
          "missing",
          "error",
          "document_id",
          "created_at",
          "confirmed_at"
        ]
      },
      "FinanzasDocumentDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FinanzasDocument"
          },
          {
            "type": "object",
            "properties": {
              "adjusted_by": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "dte_type": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "dte_type_label": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "folio": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "issue_date": {
                      "type": "string"
                    },
                    "total": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id",
                    "dte_type",
                    "dte_type_label",
                    "folio",
                    "issue_date",
                    "total"
                  ]
                }
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What it was for (manual documents)."
              },
              "files": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/FinanzasDocumentFile"
                },
                "description": "The files behind it (uploads and forwarded emails)."
              }
            },
            "required": [
              "adjusted_by",
              "description",
              "files"
            ]
          }
        ]
      },
      "FinanzasDocumentFileConfirmResult": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "created",
              "attached"
            ]
          },
          "document": {
            "$ref": "#/components/schemas/FinanzasDocumentDetail"
          },
          "file": {
            "$ref": "#/components/schemas/FinanzasDocumentFile"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The «Por resolver» case the document sits in now (open it to confirm the match); null when nothing is left to resolve."
          },
          "edited": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "read": {},
                "confirmed": {}
              }
            }
          }
        },
        "required": [
          "outcome",
          "document",
          "file",
          "case_id",
          "edited"
        ]
      },
      "FinanzasDocumentReopenResult": {
        "type": "object",
        "properties": {
          "reopened": {
            "type": "boolean",
            "description": "false when nothing settled the document (already open): idempotent."
          },
          "document": {
            "$ref": "#/components/schemas/FinanzasDocumentDetail"
          }
        },
        "required": [
          "reopened",
          "document"
        ]
      },
      "FinanzasInbox": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string",
            "example": "finanzas+k7m2q9xw4pha@vitrinadev.com"
          },
          "accepts_from": {
            "type": "string",
            "enum": [
              "workspace_members"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "rotated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "address",
          "accepts_from",
          "created_at",
          "rotated_at"
        ]
      },
      "FinanzasCartolaFormat": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "bank": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "label",
          "bank"
        ]
      },
      "FinanzasManualAccountBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120,
            "description": "The bank or card issuer, e.g. «Banco de Chile»."
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 40,
            "description": "Account or card number; only the last digits are shown."
          },
          "type": {
            "type": "string",
            "enum": [
              "cuenta_corriente",
              "cuenta_vista",
              "cuenta_ahorro",
              "tarjeta_credito",
              "linea_credito",
              "otra"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP",
              "USD"
            ],
            "description": "Default CLP."
          }
        },
        "required": [
          "name",
          "type"
        ]
      },
      "FinanzasCartolaContinuity": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "gap",
              "bootstrap",
              "not_checked"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "no_balance_in_file",
              "no_opening_balance",
              "before_opening_balance",
              "file_balance_inconsistent"
            ]
          },
          "file_opening_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "file_opening_on": {
            "type": [
              "string",
              "null"
            ]
          },
          "ledger_balance_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "gap_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "gap_from": {
            "type": [
              "string",
              "null"
            ]
          },
          "gap_to": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "status",
          "reason",
          "file_opening_minor",
          "file_opening_on",
          "ledger_balance_minor",
          "gap_minor",
          "gap_from",
          "gap_to"
        ]
      },
      "FinanzasCartolaPreview": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "needs_mapping"
            ]
          },
          "mapping_needed": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string"
              },
              "header_row": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "header": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sample": {
                "type": "array",
                "items": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "guessed": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            "required": [
              "reason",
              "header_row",
              "header",
              "sample",
              "guessed"
            ]
          },
          "format": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "key": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "auto": {
                "type": "boolean"
              }
            },
            "required": [
              "key",
              "label",
              "auto"
            ]
          },
          "mapping_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "request",
              "saved",
              "auto"
            ]
          },
          "header_row": {
            "type": [
              "integer",
              "null"
            ]
          },
          "columns": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string"
            }
          },
          "date_from": {
            "type": [
              "string",
              "null"
            ]
          },
          "date_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "counts": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "rows_read": {
                "type": "integer"
              },
              "new": {
                "type": "integer"
              },
              "duplicate": {
                "type": "integer"
              },
              "rejected": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              }
            },
            "required": [
              "rows_read",
              "new",
              "duplicate",
              "rejected",
              "skipped"
            ]
          },
          "totals": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "inbound_minor": {
                "type": "integer"
              },
              "outbound_minor": {
                "type": "integer"
              }
            },
            "required": [
              "inbound_minor",
              "outbound_minor"
            ]
          },
          "balance": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "has_balance": {
                "type": "boolean"
              },
              "opening_minor": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "closing_minor": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "chain_breaks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string"
                    },
                    "expected_minor": {
                      "type": "integer"
                    },
                    "found_minor": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "date",
                    "expected_minor",
                    "found_minor"
                  ]
                }
              }
            },
            "required": [
              "has_balance",
              "opening_minor",
              "closing_minor",
              "chain_breaks"
            ]
          },
          "continuity": {
            "$ref": "#/components/schemas/FinanzasCartolaContinuity"
          },
          "rejections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "reason": {
                  "type": "string"
                }
              },
              "required": [
                "row",
                "reason"
              ]
            }
          },
          "movements": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "date": {
                  "type": "string"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ]
                },
                "amount_minor": {
                  "type": "integer"
                },
                "description": {
                  "type": "string"
                },
                "balance_minor": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "duplicate": {
                  "type": "boolean"
                }
              },
              "required": [
                "row",
                "date",
                "direction",
                "amount_minor",
                "description",
                "balance_minor",
                "duplicate"
              ]
            }
          },
          "movements_truncated": {
            "type": "boolean"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP",
              "USD"
            ]
          }
        },
        "required": [
          "status",
          "mapping_needed",
          "format",
          "mapping_source",
          "header_row",
          "columns",
          "date_from",
          "date_to",
          "counts",
          "totals",
          "balance",
          "continuity",
          "rejections",
          "movements",
          "movements_truncated",
          "currency"
        ]
      },
      "FinanzasCartolaImportResult": {
        "type": "object",
        "properties": {
          "import": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "file_name": {
                "type": "string"
              },
              "format": {
                "type": "string"
              },
              "date_from": {
                "type": "string"
              },
              "date_to": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "file_name",
              "format",
              "date_from",
              "date_to",
              "created_at"
            ]
          },
          "counts": {
            "type": "object",
            "properties": {
              "rows_read": {
                "type": "integer"
              },
              "new": {
                "type": "integer"
              },
              "duplicate": {
                "type": "integer"
              },
              "rejected": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              }
            },
            "required": [
              "rows_read",
              "new",
              "duplicate",
              "rejected",
              "skipped"
            ]
          },
          "rejections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row": {
                  "type": "integer"
                },
                "reason": {
                  "type": "string"
                }
              },
              "required": [
                "row",
                "reason"
              ]
            }
          },
          "continuity": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCartolaContinuity"
              },
              {
                "type": "object"
              }
            ]
          },
          "gap_accepted": {
            "type": "boolean"
          },
          "closing_gap_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "mapping_saved": {
            "type": "boolean"
          },
          "account": {
            "description": "FinanzasBankAccount, refreshed."
          }
        },
        "required": [
          "import",
          "counts",
          "rejections",
          "continuity",
          "gap_accepted",
          "closing_gap_minor",
          "mapping_saved"
        ]
      },
      "FinanzasCartolaImportRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "bank_account_id": {
            "type": "string"
          },
          "file_name": {
            "type": "string"
          },
          "format": {
            "type": "string"
          },
          "date_from": {
            "type": "string"
          },
          "date_to": {
            "type": "string"
          },
          "rows_read": {
            "type": "integer"
          },
          "rows_new": {
            "type": "integer"
          },
          "rows_duplicate": {
            "type": "integer"
          },
          "rows_rejected": {
            "type": "integer"
          },
          "opening_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "closing_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "gap_minor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "gap_accepted": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "bank_account_id",
          "file_name",
          "format",
          "date_from",
          "date_to",
          "rows_read",
          "rows_new",
          "rows_duplicate",
          "rows_rejected",
          "opening_minor",
          "closing_minor",
          "gap_minor",
          "gap_accepted",
          "created_at"
        ]
      },
      "FinanzasItemType": {
        "type": "string",
        "enum": [
          "document",
          "movement"
        ]
      },
      "FinanzasClassificationSource": {
        "type": [
          "string",
          "null"
        ],
        "enum": [
          "manual",
          "rule",
          "ia",
          "chipax"
        ]
      },
      "FinanzasClassificationSummaries": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "$ref": "#/components/schemas/FinanzasItemType"
                },
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "unclassified",
                    "classified"
                  ]
                },
                "source": {
                  "$ref": "#/components/schemas/FinanzasClassificationSource"
                },
                "rule": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "accounts": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "id",
                      "name",
                      "path"
                    ]
                  }
                },
                "split": {
                  "type": "boolean"
                }
              },
              "required": [
                "type",
                "id",
                "state",
                "source",
                "rule",
                "accounts",
                "split"
              ]
            }
          }
        },
        "required": [
          "items"
        ]
      },
      "FinanzasClassification": {
        "type": "object",
        "properties": {
          "item": {
            "type": "object",
            "properties": {
              "type": {
                "$ref": "#/components/schemas/FinanzasItemType"
              },
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "amount_clp": {
                "type": "integer"
              },
              "day": {
                "type": "string"
              },
              "contraparte": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              "counterparty_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "direction": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "inbound",
                  "outbound"
                ]
              },
              "document_kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "expense_document_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              }
            },
            "required": [
              "type",
              "id",
              "amount_clp",
              "day",
              "contraparte",
              "counterparty_name",
              "description",
              "direction",
              "document_kind",
              "expense_document_id"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "unclassified",
              "classified"
            ]
          },
          "source": {
            "$ref": "#/components/schemas/FinanzasClassificationSource"
          },
          "rule": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "account": {
                  "$ref": "#/components/schemas/FinanzasAccountRef"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "period": {
                  "type": "string"
                },
                "source": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/FinanzasClassificationSource"
                    },
                    {
                      "type": "string"
                    }
                  ]
                },
                "rule": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "classified_by": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "created_at": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "account",
                "amount_clp",
                "period",
                "source",
                "rule",
                "classified_by",
                "created_at"
              ]
            }
          },
          "history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "classify",
                    "reclassify",
                    "unclassify"
                  ]
                },
                "actor": {
                  "type": "string",
                  "enum": [
                    "user",
                    "rule",
                    "ia",
                    "chipax"
                  ]
                },
                "actor_user_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "rule_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "before": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "account_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "amount_clp": {
                        "type": "integer"
                      },
                      "period": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "account_id",
                      "amount_clp",
                      "period"
                    ]
                  }
                },
                "after": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "account_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "amount_clp": {
                        "type": "integer"
                      },
                      "period": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "account_id",
                      "amount_clp",
                      "period"
                    ]
                  }
                },
                "created_at": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "action",
                "actor",
                "actor_user_id",
                "rule_id",
                "before",
                "after",
                "created_at"
              ]
            }
          }
        },
        "required": [
          "item",
          "state",
          "source",
          "rule",
          "lines"
        ]
      },
      "FinanzasClassifyBody": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The account of the plan de cuentas. Or name it with `account_name`."
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 300,
            "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
          },
          "period": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Accrual month, `YYYY-MM`.",
            "example": "2026-10"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "account_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "The account of the plan de cuentas. Or name it with `account_name`."
                },
                "account_name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "minLength": 1,
                  "maxLength": 300,
                  "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
                },
                "amount_clp": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 9007199254740991,
                  "description": "Whole CLP. Required when there is more than one line."
                },
                "period": {
                  "type": "string",
                  "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                  "description": "Accrual month, `YYYY-MM`.",
                  "example": "2026-10"
                }
              },
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "The split («Dividir»): lines summing EXACTLY to the item total (whole pesos; a remainder is refused). Omit for one line with the whole amount on `account_id` / `account_name`."
          }
        },
        "additionalProperties": false
      },
      "FinanzasItemRef": {
        "type": "object",
        "properties": {
          "type": {
            "$ref": "#/components/schemas/FinanzasItemType"
          },
          "id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "type",
          "id"
        ],
        "additionalProperties": false
      },
      "FinanzasBulkClassifyResult": {
        "type": "object",
        "properties": {
          "classified": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasItemRef"
            }
          }
        },
        "required": [
          "classified",
          "items"
        ]
      },
      "FinanzasBulkClassifyBody": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasItemRef"
            },
            "minItems": 1,
            "maxItems": 500
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The account of the plan de cuentas. Or name it with `account_name`."
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 300,
            "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
          },
          "period": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Accrual month for every item; default each item’s own month.",
            "example": "2026-10"
          }
        },
        "required": [
          "items"
        ],
        "additionalProperties": false
      },
      "FinanzasExpensesResult": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "expenses": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "movement_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "document_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "day": {
                  "type": "string"
                }
              },
              "required": [
                "movement_id",
                "document_id",
                "amount_clp",
                "day"
              ]
            }
          }
        },
        "required": [
          "count",
          "expenses"
        ]
      },
      "FinanzasExpensesFromMovementsBody": {
        "type": "object",
        "properties": {
          "movement_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "maxItems": 500
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The account of the plan de cuentas. Or name it with `account_name`."
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 300,
            "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Shared description; default each movement’s bank text."
          },
          "period": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
            "description": "Accrual month for every gasto; default each movement’s month.",
            "example": "2026-10"
          }
        },
        "required": [
          "movement_ids"
        ],
        "additionalProperties": false
      },
      "FinanzasRules": {
        "type": "object",
        "properties": {
          "rules": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasRule"
            }
          }
        },
        "required": [
          "rules"
        ]
      },
      "FinanzasRuleCreateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "contraparte_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contraparte_rut": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "Plain-language alternative to `contraparte_id`: its RUT."
          },
          "contraparte_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Plain-language alternative to `contraparte_id`: its name. Ambiguous → 422 `contraparte_ambiguous` with `details.candidates`."
          },
          "text_contains": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "«Descripción contiene»: case- and accent-insensitive substring of the counterparty name (and, on movements, the bank description)."
          },
          "amount_min_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "amount_max_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound"
            ],
            "description": "Movement rules only: inbound = abono, outbound = cargo."
          },
          "document_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "purchase",
              "sale",
              "honorario",
              "manual_expense",
              "tax"
            ],
            "description": "Document rules only: the register."
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The account of the plan de cuentas. Or name it with `account_name`."
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 300,
            "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
          },
          "accrual": {
            "type": "string",
            "enum": [
              "issue_month",
              "previous_month"
            ],
            "description": "The month the rule books a document in: `issue_month` (its issue date, the default) or `previous_month` («mes anterior»: a supplier that bills month M early in M+1). Vitrina learns it from history; a person setting it here wins for good."
          },
          "target": {
            "type": "string",
            "enum": [
              "document",
              "movement"
            ],
            "description": "Default `document` (or the type of `from_item`)."
          },
          "from_item": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasItemRef"
              },
              {
                "description": "«Siempre clasificar así»: prefill target, Contraparte (or its name as text), direction and account from this item and its classification. Explicit fields win."
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "FinanzasRuleUpdateBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "contraparte_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contraparte_rut": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 20,
            "description": "Plain-language alternative to `contraparte_id`: its RUT."
          },
          "contraparte_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Plain-language alternative to `contraparte_id`: its name. Ambiguous → 422 `contraparte_ambiguous` with `details.candidates`."
          },
          "text_contains": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "«Descripción contiene»: case- and accent-insensitive substring of the counterparty name (and, on movements, the bank description)."
          },
          "amount_min_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "amount_max_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "direction": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "inbound",
              "outbound"
            ],
            "description": "Movement rules only: inbound = abono, outbound = cargo."
          },
          "document_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "purchase",
              "sale",
              "honorario",
              "manual_expense",
              "tax"
            ],
            "description": "Document rules only: the register."
          },
          "account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The account of the plan de cuentas. Or name it with `account_name`."
          },
          "account_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 300,
            "description": "Plain-language alternative to `account_id`: the account name or path («Laboratorio», «Gastos de administración › Arriendo»), matched without case or accents. Ambiguous → 422 `account_ambiguous` with `details.candidates`."
          },
          "accrual": {
            "type": "string",
            "enum": [
              "issue_month",
              "previous_month"
            ],
            "description": "The month the rule books a document in: `issue_month` (its issue date, the default) or `previous_month` («mes anterior»: a supplier that bills month M early in M+1). Vitrina learns it from history; a person setting it here wins for good."
          },
          "target": {
            "type": "string",
            "enum": [
              "document",
              "movement"
            ],
            "description": "`document`: classifies documents. `movement`: classifies bank movements that have no document (a gardener paid by transfer)."
          }
        },
        "additionalProperties": false
      },
      "FinanzasRulePending": {
        "type": "object",
        "properties": {
          "rule_id": {
            "type": "string",
            "format": "uuid"
          },
          "count": {
            "type": "integer"
          },
          "total_clp": {
            "type": "integer"
          },
          "sample": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "$ref": "#/components/schemas/FinanzasItemType"
                },
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "day": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "type",
                "id",
                "day",
                "amount_clp",
                "counterparty_name",
                "description"
              ]
            }
          }
        },
        "required": [
          "rule_id",
          "count",
          "total_clp",
          "sample"
        ]
      },
      "FinanzasRuleApplyResult": {
        "type": "object",
        "properties": {
          "rule_id": {
            "type": "string",
            "format": "uuid"
          },
          "applied": {
            "type": "integer"
          }
        },
        "required": [
          "rule_id",
          "applied"
        ]
      },
      "FinanzasTransferAccount": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "bank_account_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Finanzas bank account; null for an own account Vitrina does not hold (not connected or not registered yet)."
          },
          "institution_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "last4": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": "string",
            "description": "Ready to show: «BCI ··9819», «Cuenta ··4521» without a bank."
          }
        },
        "required": [
          "bank_account_id",
          "institution_name",
          "last4",
          "label"
        ],
        "description": "The side the money left; null = an own account Vitrina does not hold."
      },
      "FinanzasOwnTransfer": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "from": {
            "$ref": "#/components/schemas/FinanzasTransferAccount"
          },
          "to": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasTransferAccount"
              },
              {
                "description": "The side the money reached; null = an own account Vitrina does not hold."
              }
            ]
          },
          "paired_movement_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The movement on the other side, when Vitrina holds it (link to it)."
          }
        },
        "required": [
          "from",
          "to",
          "paired_movement_id"
        ],
        "description": "Only on `target = transfer`: «Traspaso entre tus cuentas», both sides ready to show."
      },
      "FinanzasLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "target": {
            "type": "string",
            "enum": [
              "document",
              "payment",
              "transfer",
              "third_party",
              "cash_deposit"
            ]
          },
          "amount_clp": {
            "type": "integer"
          },
          "rounding_clp": {
            "type": "integer",
            "description": "Pesos the document was credited beyond `amount_clp` (negative when the movement paid more); |x| ≤ 10, only on a matcher link of a known supplier (reason `rounding`). 0 = exact. The document is paid by `amount_clp + rounding_clp`; the movement explains `amount_clp`."
          },
          "actor": {
            "type": "string",
            "enum": [
              "person",
              "rule",
              "match",
              "ai",
              "import"
            ],
            "description": "person = Manual, rule = Regla, match = Coincidencia, ai = IA, import = Importación (copied from Chipax, Finanzas 16; reason `chipax_import`)."
          },
          "actor_user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "undone_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "movement_id": {
            "type": "string"
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issue_date": {
                "type": "string"
              },
              "total_clp": {
                "type": "integer"
              },
              "counterparty_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "kind",
              "dte_type",
              "folio",
              "issue_date",
              "total_clp",
              "counterparty_name"
            ]
          },
          "payment": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "day": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              },
              "patient": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              }
            },
            "required": [
              "id",
              "day",
              "amount_clp",
              "patient"
            ]
          },
          "paired_movement": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "day": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              },
              "direction": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "bank_account_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "day",
              "amount_clp",
              "direction",
              "bank_account_name"
            ]
          },
          "cash_day": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "day": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "day",
              "amount_clp"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "payout": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "movement_id": {
                "type": "string"
              },
              "day": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              },
              "processor_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "movement_id",
              "day",
              "amount_clp",
              "processor_name"
            ],
            "description": "Finanzas 36: set on a link with reason `payout_deduction`: the commission factura (`document`) was paid by deduction from this card payout (the processor kept it). Show «Pagada con descuento» and «Descontada del abono de {processor_name} del {day}», linking to `movement_id`. Such a link takes nothing from the payout's cash. Null otherwise."
          },
          "own_transfer": {
            "$ref": "#/components/schemas/FinanzasOwnTransfer"
          },
          "undoable": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "target",
          "amount_clp",
          "rounding_clp",
          "actor",
          "actor_user_id",
          "reasons",
          "note",
          "created_at",
          "undone_at",
          "movement_id",
          "document",
          "payment",
          "paired_movement",
          "cash_day",
          "contraparte",
          "payout",
          "own_transfer",
          "undoable"
        ]
      },
      "FinanzasLegacyLink": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "`settlement:<uuid>` or `payment:<uuid>`."
          },
          "kind": {
            "type": "string",
            "enum": [
              "settlement",
              "payment"
            ]
          },
          "target": {
            "type": "string",
            "enum": [
              "payment"
            ]
          },
          "amount_clp": {
            "type": "integer"
          },
          "actor": {
            "type": "string",
            "enum": [
              "person",
              "match"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "detail": {
            "type": "object",
            "properties": {
              "settlement_kind": {
                "type": "string"
              },
              "business_day": {
                "type": "string"
              },
              "commission_clp": {
                "type": "integer"
              },
              "payment_ids": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "paid_on": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "instrument": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "undoable": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "kind",
          "target",
          "amount_clp",
          "actor",
          "created_at",
          "detail",
          "undoable"
        ]
      },
      "FinanzasMovementReconciliation": {
        "type": "object",
        "properties": {
          "movement": {
            "$ref": "#/components/schemas/FinanzasMovement"
          },
          "money_mirrored": {
            "type": "boolean",
            "description": "The clinic money of this movement is mirrored from its clinic system: it links only to those payments, as evidence."
          },
          "payer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "patient": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              }
            },
            "required": [
              "name",
              "email",
              "patient"
            ]
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasLink"
            }
          },
          "legacy_links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasLegacyLink"
            }
          },
          "commission": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount_clp": {
                "type": "integer"
              },
              "source": {
                "type": "string",
                "enum": [
                  "mercado_pago",
                  "card_settlement"
                ]
              },
              "account": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              }
            },
            "required": [
              "amount_clp",
              "source",
              "account"
            ]
          }
        },
        "required": [
          "movement",
          "money_mirrored",
          "payer",
          "links",
          "legacy_links",
          "commission"
        ]
      },
      "FinanzasMovementCandidates": {
        "type": "object",
        "properties": {
          "pending_clp": {
            "type": "integer"
          },
          "money_mirrored": {
            "type": "boolean"
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "dte_type": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "folio": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "issue_date": {
                  "type": "string"
                },
                "due_date": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "total_clp": {
                  "type": "integer"
                },
                "outstanding_clp": {
                  "type": "integer"
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "contraparte": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "signal": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "folio",
                    "contraparte",
                    "amount",
                    "search"
                  ]
                },
                "suggested_amount_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "kind",
                "dte_type",
                "folio",
                "issue_date",
                "due_date",
                "total_clp",
                "outstanding_clp",
                "counterparty_name",
                "contraparte",
                "signal",
                "suggested_amount_clp"
              ]
            }
          },
          "payments": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "caja",
                    "mirror"
                  ]
                },
                "day": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "instrument": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "patient": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                }
              },
              "required": [
                "id",
                "source",
                "day",
                "amount_clp",
                "instrument",
                "patient"
              ]
            }
          },
          "transfers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "bank_account_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "day",
                "direction",
                "amount_clp",
                "bank_account_name",
                "description"
              ]
            }
          },
          "cash_days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "caja",
                    "boletas"
                  ]
                },
                "remaining_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "day",
                "source",
                "remaining_clp"
              ]
            }
          }
        },
        "required": [
          "pending_clp",
          "money_mirrored",
          "documents",
          "payments",
          "transfers",
          "cash_days"
        ]
      },
      "FinanzasDocumentReconciliation": {
        "type": "object",
        "properties": {
          "document": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issue_date": {
                "type": "string"
              },
              "due_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "counterparty_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "contraparte": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              "total_clp": {
                "type": "integer"
              },
              "paid_clp": {
                "type": "integer"
              },
              "outstanding_clp": {
                "type": "integer"
              },
              "payment_state": {
                "type": "string",
                "enum": [
                  "por_pagar",
                  "parcial",
                  "pagado",
                  "vencido_heredado"
                ],
                "description": "`vencido_heredado` is retired (Finanzas 50): only rows written before it, outside that backfill, still carry it."
              }
            },
            "required": [
              "id",
              "kind",
              "dte_type",
              "folio",
              "issue_date",
              "due_date",
              "counterparty_name",
              "contraparte",
              "total_clp",
              "paid_clp",
              "outstanding_clp",
              "payment_state"
            ]
          },
          "links": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasLink"
            }
          },
          "candidates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "pending_clp": {
                  "type": "integer"
                },
                "description": {
                  "type": "string"
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "bank_account_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "signal": {
                  "type": "string",
                  "enum": [
                    "folio",
                    "contraparte",
                    "amount",
                    "search"
                  ]
                },
                "suggested_amount_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "day",
                "direction",
                "amount_clp",
                "pending_clp",
                "description",
                "counterparty_name",
                "bank_account_name",
                "signal",
                "suggested_amount_clp"
              ]
            }
          }
        },
        "required": [
          "document",
          "links",
          "candidates"
        ]
      },
      "FinanzasEngineResult": {
        "type": "object",
        "properties": {
          "skipped": {
            "type": "boolean"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "movements_seen": {
            "type": "integer"
          },
          "applied": {
            "type": "object",
            "properties": {
              "documents": {
                "type": "integer"
              },
              "transfers": {
                "type": "integer"
              },
              "cash_deposits": {
                "type": "integer"
              },
              "payments": {
                "type": "integer"
              },
              "expenses": {
                "type": "integer",
                "description": "Gastos created from a movement («Crear gasto») linked to it."
              }
            },
            "required": [
              "documents",
              "transfers",
              "cash_deposits",
              "payments",
              "expenses"
            ]
          },
          "ambiguous": {
            "type": "integer"
          }
        },
        "required": [
          "skipped",
          "from",
          "to",
          "movements_seen",
          "applied",
          "ambiguous"
        ]
      },
      "FinanzasCashPending": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "caja",
                    "boletas"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "deposited_clp": {
                  "type": "integer"
                },
                "remaining_clp": {
                  "type": "integer"
                },
                "detail": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "number"
                  }
                }
              },
              "required": [
                "id",
                "day",
                "source",
                "amount_clp",
                "deposited_clp",
                "remaining_clp",
                "detail"
              ]
            }
          },
          "total_remaining_clp": {
            "type": "integer"
          },
          "pending": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCashPendingSummary"
              },
              {
                "description": "The one «efectivo por depositar» (last 60 days up to yesterday), identical to the Flujo de caja and Movimientos. `items` is the standing list for the requested period (it feeds the Por resolver matcher)."
              }
            ]
          }
        },
        "required": [
          "items",
          "total_remaining_clp",
          "pending"
        ]
      },
      "FinanzasCommissions": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "movement_id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "mercado_pago",
                    "card_settlement"
                  ]
                },
                "bank_account_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "account": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                }
              },
              "required": [
                "movement_id",
                "day",
                "amount_clp",
                "source",
                "bank_account_name",
                "account"
              ]
            }
          },
          "total_clp": {
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total_clp"
        ]
      },
      "FinanzasCaseList": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "reconcile",
                    "transfer",
                    "classify",
                    "missing_document",
                    "unknown",
                    "ads_charge",
                    "merge"
                  ],
                  "description": "reconcile = movements ↔ documents; transfer = between own accounts; classify = to an account; missing_document = «Falta documento» (money out classified with no document); unknown = no proposal yet; ads_charge = a Meta / Google card charge Vitrina could not confirm («Cargo de publicidad sin confirmar» / «Sin conexión de publicidad», Finanzas 30): the proposal links each movement to its own ads document; merge = a «unir» Caso (Finanzas 58): two Contrapartes with similar names and no shared RUT, proposed to be merged (`merge` holds the pair; it has no movements or documents and moves no money)."
                },
                "title": {
                  "type": "string"
                },
                "summary": {
                  "type": "string"
                },
                "contraparte": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "rut": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "rut"
                  ]
                },
                "amount_at_stake_clp": {
                  "type": "integer"
                },
                "item_count": {
                  "type": "integer"
                },
                "movement_count": {
                  "type": "integer"
                },
                "document_count": {
                  "type": "integer"
                },
                "ready": {
                  "type": "boolean"
                },
                "opened_at": {
                  "type": "string"
                },
                "latest_day": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The newest day the case touches (a movement, or a document it needs classified)."
                },
                "fingerprint": {
                  "type": "string",
                  "description": "The case as listed; «Confirmar en bloque» sends it back (a changed case is refused)."
                }
              },
              "required": [
                "id",
                "kind",
                "title",
                "summary",
                "contraparte",
                "amount_at_stake_clp",
                "item_count",
                "movement_count",
                "document_count",
                "ready",
                "opened_at",
                "latest_day",
                "fingerprint"
              ]
            }
          },
          "total": {
            "type": "integer"
          },
          "amount_at_stake_clp": {
            "type": "integer"
          },
          "since": {
            "type": "string",
            "description": "First day of last month: `period=recent` keeps the cases touching it or later; older ones are «Historial»."
          },
          "counts_by_month": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "`YYYY-MM`."
                },
                "open": {
                  "type": "integer"
                },
                "amount_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "month",
                "open",
                "amount_clp"
              ]
            },
            "description": "Open cases per month, newest first, all kinds, ignoring `kind`/`month`/`period`: the month filter’s options (the sum is the open total every surface shows). Months «Cerrado antes de Vitrina» are never listed: they hold no case."
          },
          "counts": {
            "type": "object",
            "properties": {
              "recent": {
                "type": "integer"
              },
              "history": {
                "type": "integer"
              },
              "needs_person": {
                "type": "integer"
              },
              "ready": {
                "type": "integer"
              }
            },
            "required": [
              "recent",
              "history",
              "needs_person",
              "ready"
            ],
            "description": "Open cases by period (every period) and, within the `period` asked, how many need a person vs are ready to confirm."
          },
          "waiting": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "amount_clp": {
                "type": "integer"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "movement_id": {
                      "type": "string"
                    },
                    "day": {
                      "type": "string"
                    },
                    "amount_clp": {
                      "type": "integer"
                    },
                    "contraparte": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ]
                    },
                    "lag_days": {
                      "type": "integer"
                    },
                    "wait_until": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "movement_id",
                    "day",
                    "amount_clp",
                    "contraparte",
                    "lag_days",
                    "wait_until"
                  ]
                }
              }
            },
            "required": [
              "count",
              "amount_clp",
              "items"
            ],
            "description": "«Esperando factura»: payments to a supplier that always invoices, inside its usual wait for the document. No case and no classification until then (the factura links instead); after `wait_until` the payment opens a case asking for it."
          }
        },
        "required": [
          "items",
          "total",
          "amount_at_stake_clp",
          "since",
          "counts_by_month",
          "counts",
          "waiting"
        ]
      },
      "FinanzasCaseCount": {
        "type": "object",
        "properties": {
          "open": {
            "type": "integer"
          },
          "amount_at_stake_clp": {
            "type": "integer"
          },
          "by_kind": {
            "type": "object",
            "properties": {
              "reconcile": {
                "type": "integer"
              },
              "transfer": {
                "type": "integer"
              },
              "classify": {
                "type": "integer"
              },
              "missing_document": {
                "type": "integer"
              },
              "unknown": {
                "type": "integer"
              },
              "ads_charge": {
                "type": "integer"
              },
              "merge": {
                "type": "integer"
              }
            },
            "required": [
              "reconcile",
              "transfer",
              "classify",
              "missing_document",
              "unknown",
              "ads_charge",
              "merge"
            ]
          }
        },
        "required": [
          "open",
          "amount_at_stake_clp",
          "by_kind"
        ]
      },
      "FinanzasCase": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "reconcile",
              "transfer",
              "classify",
              "missing_document",
              "unknown",
              "ads_charge",
              "merge"
            ],
            "description": "reconcile = movements ↔ documents; transfer = between own accounts; classify = to an account; missing_document = «Falta documento» (money out classified with no document); unknown = no proposal yet; ads_charge = a Meta / Google card charge Vitrina could not confirm («Cargo de publicidad sin confirmar» / «Sin conexión de publicidad», Finanzas 30): the proposal links each movement to its own ads document; merge = a «unir» Caso (Finanzas 58): two Contrapartes with similar names and no shared RUT, proposed to be merged (`merge` holds the pair; it has no movements or documents and moves no money)."
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ]
          },
          "resolution": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "confirmed",
              "cleared",
              "closed_imported"
            ]
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name",
              "rut"
            ]
          },
          "title": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "amount_at_stake_clp": {
            "type": "integer"
          },
          "item_count": {
            "type": "integer"
          },
          "ready": {
            "type": "boolean",
            "description": "Every item has a proposed action: «Resolver» (POST …/confirm) clears the whole case."
          },
          "fingerprint": {
            "type": "string"
          },
          "movements": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "direction": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "pending_clp": {
                  "type": "integer"
                },
                "description": {
                  "type": "string"
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "bank_account": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "open",
                    "missing_document"
                  ]
                },
                "classification": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "account": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "path": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "path"
                      ]
                    }
                  },
                  "required": [
                    "account"
                  ]
                }
              },
              "required": [
                "id",
                "day",
                "direction",
                "amount_clp",
                "pending_clp",
                "description",
                "counterparty_name",
                "bank_account",
                "role",
                "classification"
              ]
            }
          },
          "documents": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "purchase",
                    "sale",
                    "honorario",
                    "manual_expense",
                    "tax"
                  ]
                },
                "dte_type": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "folio": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "issue_date": {
                  "type": "string"
                },
                "due_date": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "total_clp": {
                  "type": "integer",
                  "description": "What is paid: the líquido on a boleta de honorarios, the total otherwise."
                },
                "gross_clp": {
                  "type": "integer",
                  "description": "What the document costs: the gross total. Differs from total_clp only on a boleta de honorarios."
                },
                "outstanding_clp": {
                  "type": "integer"
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "unclassified",
                    "candidate"
                  ]
                },
                "classified": {
                  "type": "boolean"
                }
              },
              "required": [
                "id",
                "kind",
                "dte_type",
                "folio",
                "issue_date",
                "due_date",
                "total_clp",
                "gross_clp",
                "outstanding_clp",
                "counterparty_name",
                "role",
                "classified"
              ]
            }
          },
          "cash_days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "remaining_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "day",
                "remaining_clp"
              ]
            }
          },
          "proposal": {
            "type": "object",
            "properties": {
              "actions": {
                "type": "array",
                "items": {
                  "anyOf": [
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "link"
                          ]
                        },
                        "movement_id": {
                          "type": "string"
                        },
                        "links": {
                          "type": "array",
                          "items": {
                            "anyOf": [
                              {
                                "type": "object",
                                "properties": {
                                  "target": {
                                    "type": "string",
                                    "enum": [
                                      "document"
                                    ]
                                  },
                                  "document_id": {
                                    "type": "string"
                                  },
                                  "amount_clp": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "target",
                                  "document_id",
                                  "amount_clp"
                                ]
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "target": {
                                    "type": "string",
                                    "enum": [
                                      "transfer"
                                    ]
                                  },
                                  "paired_movement_id": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "target",
                                  "paired_movement_id"
                                ]
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "target": {
                                    "type": "string",
                                    "enum": [
                                      "cash_deposit"
                                    ]
                                  },
                                  "cash_day_id": {
                                    "type": "string"
                                  },
                                  "amount_clp": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "target",
                                  "cash_day_id",
                                  "amount_clp"
                                ]
                              }
                            ]
                          }
                        },
                        "replace_classification": {
                          "type": "boolean"
                        },
                        "reasons": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Stable codes: folio_and_amount, exact_amount, sum_of_documents, sum_of_movements, nearest_date (a pago a cuenta on the documents issued nearest the payment; cases built before 2026-11 may carry oldest_first), cash_deposit, own_account_transfer, history, document_arrived, no_document, ia (the IA proposed it: below the auto-apply threshold, or the IA switch is off)."
                        }
                      },
                      "required": [
                        "type",
                        "movement_id",
                        "links",
                        "replace_classification",
                        "reasons"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "classify"
                          ]
                        },
                        "item": {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "document",
                                "movement"
                              ]
                            },
                            "id": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "type",
                            "id"
                          ]
                        },
                        "account": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string"
                            },
                            "name": {
                              "type": "string"
                            },
                            "path": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "path"
                          ]
                        },
                        "reasons": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Stable codes: folio_and_amount, exact_amount, sum_of_documents, sum_of_movements, nearest_date (a pago a cuenta on the documents issued nearest the payment; cases built before 2026-11 may carry oldest_first), cash_deposit, own_account_transfer, history, document_arrived, no_document, ia (the IA proposed it: below the auto-apply threshold, or the IA switch is off)."
                        }
                      },
                      "required": [
                        "type",
                        "item",
                        "account",
                        "reasons"
                      ]
                    },
                    {
                      "type": "object",
                      "properties": {
                        "type": {
                          "type": "string",
                          "enum": [
                            "accept_without_document"
                          ]
                        },
                        "movement_id": {
                          "type": "string"
                        },
                        "reasons": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "Stable codes: folio_and_amount, exact_amount, sum_of_documents, sum_of_movements, nearest_date (a pago a cuenta on the documents issued nearest the payment; cases built before 2026-11 may carry oldest_first), cash_deposit, own_account_transfer, history, document_arrived, no_document, ia (the IA proposed it: below the auto-apply threshold, or the IA switch is off)."
                        }
                      },
                      "required": [
                        "type",
                        "movement_id",
                        "reasons"
                      ]
                    }
                  ]
                }
              },
              "why": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Spanish sentences: why Vitrina proposes this."
              }
            },
            "required": [
              "actions",
              "why"
            ]
          },
          "unresolved": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "document",
                    "movement"
                  ]
                },
                "id": {
                  "type": "string"
                }
              },
              "required": [
                "type",
                "id"
              ]
            }
          },
          "awaiting_document": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Movement ids of payments to a supplier that always invoices, past its usual wait: the factura is missing. Never classified on their own nor asked to the IA."
          },
          "ads": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "platform": {
                "type": "string",
                "enum": [
                  "meta_ads",
                  "google_ads"
                ]
              },
              "verdict": {
                "type": "string",
                "enum": [
                  "unconfirmed",
                  "no_connection"
                ]
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "movement_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "document_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "reason": {
                      "type": "string",
                      "description": "no_charge | count_mismatch | customer_id_unknown | spend_does_not_cover | ambiguous | refund_unmatched | undone_by_person | no_connection"
                    },
                    "customer_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reference": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "candidates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "charge_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "ad_account_id": {
                            "type": "string"
                          },
                          "meta_transaction_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "event_time": {
                            "type": "string"
                          },
                          "local_date": {
                            "type": "string"
                          },
                          "amount_minor": {
                            "type": "integer"
                          },
                          "currency": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "charge_id",
                          "ad_account_id",
                          "meta_transaction_id",
                          "event_time",
                          "local_date",
                          "amount_minor",
                          "currency"
                        ]
                      }
                    },
                    "candidate_accounts": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  },
                  "required": [
                    "movement_id",
                    "document_id",
                    "reason",
                    "customer_id",
                    "reference",
                    "candidates",
                    "candidate_accounts"
                  ]
                }
              }
            },
            "required": [
              "platform",
              "verdict",
              "items"
            ],
            "description": "Finanzas 30, `ads_charge` cases only: the matcher's verdict per movement and the Meta charges / ad accounts that could be it. Amounts in the smallest unit of `currency`."
          },
          "merge": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "keep": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "movement_count": {
                    "type": "integer"
                  },
                  "document_count": {
                    "type": "integer"
                  }
                },
                "required": [
                  "id",
                  "name",
                  "rut",
                  "movement_count",
                  "document_count"
                ]
              },
              "merge": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "movement_count": {
                    "type": "integer"
                  },
                  "document_count": {
                    "type": "integer"
                  }
                },
                "required": [
                  "id",
                  "name",
                  "rut",
                  "movement_count",
                  "document_count"
                ]
              },
              "score": {
                "type": "number"
              },
              "moves": {
                "type": "object",
                "properties": {
                  "movements": {
                    "type": "integer"
                  },
                  "documents": {
                    "type": "integer"
                  }
                },
                "required": [
                  "movements",
                  "documents"
                ]
              }
            },
            "required": [
              "keep",
              "merge",
              "score",
              "moves"
            ],
            "description": "Finanzas 58, `merge` («unir») cases only: the pair. `keep` stays, `merge` is merged into it; `moves` is what passes to `keep`. Resolve with the confirm route (merge, undoable in «Hecho por Vitrina») or the dismiss route («No son la misma»)."
          },
          "opened_at": {
            "type": "string"
          },
          "computed_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "key",
          "kind",
          "status",
          "resolution",
          "resolved_at",
          "contraparte",
          "title",
          "summary",
          "amount_at_stake_clp",
          "item_count",
          "ready",
          "fingerprint",
          "movements",
          "documents",
          "cash_days",
          "proposal",
          "unresolved",
          "opened_at",
          "computed_at"
        ]
      },
      "FinanzasCasesBulkConfirmResult": {
        "type": "object",
        "properties": {
          "confirmed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "case_id": {
                  "type": "string"
                },
                "applied": {
                  "type": "object",
                  "properties": {
                    "links": {
                      "type": "integer"
                    },
                    "classifications": {
                      "type": "integer"
                    },
                    "accepted": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "links",
                    "classifications",
                    "accepted"
                  ]
                }
              },
              "required": [
                "case_id",
                "applied"
              ]
            }
          },
          "failed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "case_id": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "description": "resolved | fingerprint_mismatch | proposal_incomplete | refused"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "case_id",
                "reason",
                "message"
              ]
            }
          }
        },
        "required": [
          "confirmed",
          "failed"
        ]
      },
      "FinanzasCasesBulkConfirmBody": {
        "type": "object",
        "properties": {
          "cases": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "fingerprint": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128
                }
              },
              "required": [
                "id",
                "fingerprint"
              ],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 100,
            "description": "The ready cases to confirm with their own proposals, with the fingerprint each was read with."
          }
        },
        "required": [
          "cases"
        ],
        "additionalProperties": false
      },
      "FinanzasCaseConfirmResult": {
        "type": "object",
        "properties": {
          "case_id": {
            "type": "string"
          },
          "applied": {
            "type": "object",
            "properties": {
              "links": {
                "type": "integer"
              },
              "classifications": {
                "type": "integer"
              },
              "accepted": {
                "type": "integer"
              }
            },
            "required": [
              "links",
              "classifications",
              "accepted"
            ]
          },
          "remaining_case": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCase"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "next_case_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "merged": {
            "type": "object",
            "properties": {
              "merge_id": {
                "type": "string"
              },
              "survivor_id": {
                "type": "string"
              },
              "merged_id": {
                "type": "string"
              }
            },
            "required": [
              "merge_id",
              "survivor_id",
              "merged_id"
            ],
            "description": "Finanzas 58, `merge` («unir») cases only: the merge the confirm made. `merge_id` is the «Deshacer» id in «Hecho por Vitrina»."
          },
          "descriptors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "descriptor": {
                  "type": "string"
                },
                "contraparte_id": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "learned",
                    "forgotten"
                  ]
                }
              },
              "required": [
                "descriptor",
                "contraparte_id",
                "status"
              ]
            },
            "description": "Card / automatic-debit descriptors this confirm tied to a Contraparte (`learned`: a movement that named no RUT or account was confirmed against documents of one Contraparte; from now on the engine knows the supplier, shown once as «Desde ahora reconocemos “MERPAGO*MERCADOL” como …»), or untied (`forgotten`: confirmed for a second Contraparte, so it names neither). Listed and removable as the Contraparte's aliases (kind `descriptor`)."
          }
        },
        "required": [
          "case_id",
          "applied",
          "remaining_case",
          "next_case_id",
          "descriptors"
        ]
      },
      "FinanzasCaseDismissResult": {
        "type": "object",
        "properties": {
          "case_id": {
            "type": "string"
          },
          "next_case_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "case_id",
          "next_case_id"
        ]
      },
      "FinanzasNoDocumentUndo": {
        "type": "object",
        "properties": {
          "movement_id": {
            "type": "string"
          },
          "case_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "movement_id",
          "case_id"
        ]
      },
      "FinanzasAutomaticAction": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "link",
              "classification"
            ],
            "description": "link = a reconciliation link (Finanzas 08); classification = a classification by a rule or the IA (Finanzas 07)."
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "actor": {
            "type": "string",
            "enum": [
              "rule",
              "match",
              "ai"
            ],
            "description": "rule = Regla, match = Coincidencia, ai = IA."
          },
          "label": {
            "type": "string",
            "description": "What Vitrina did, one Spanish sentence."
          },
          "day": {
            "type": "string",
            "description": "The item's day, `YYYY-MM-DD`."
          },
          "amount_clp": {
            "type": "integer"
          },
          "applied_at": {
            "type": "string"
          },
          "movement": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "day": {
                "type": "string"
              },
              "direction": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "inbound",
                  "outbound"
                ]
              },
              "amount_clp": {
                "type": "integer"
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "day",
              "direction",
              "amount_clp",
              "description"
            ]
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "kind": {
                "type": "string"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issue_date": {
                "type": "string"
              },
              "total_clp": {
                "type": "integer"
              },
              "counterparty_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "kind",
              "dte_type",
              "folio",
              "issue_date",
              "total_clp",
              "counterparty_name"
            ]
          },
          "target": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "document",
              "payment",
              "transfer",
              "third_party",
              "cash_deposit"
            ]
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "evidence": {
            "type": [
              "string",
              "null"
            ],
            "description": "Links Vitrina made from ad data (Finanzas 30, reasons include `ads_charge`): the evidence in one Spanish line, e.g. «Meta Ads cobró $4.211 el 22 oct a las 09:14 (cuenta …)» or «Gasto del 22 al 24 oct: $47.824 en la cuenta … de Google Ads; el cargo lo cubre». A matcher link of a known supplier within rounding (reason `rounding`) or paid before its document was issued (`paid_before_issue`) says so here, e.g. «El pago fue $1 menor que el documento; la diferencia quedó como redondeo y el documento se cerró.». Null otherwise."
          },
          "own_transfer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasOwnTransfer"
              },
              {
                "description": "Only on a `transfer` link (Finanzas 32, D5): «BCI ··9819 → Itaú ··9627», with the paired movement when Vitrina holds it. Null otherwise."
              }
            ]
          },
          "account": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name",
              "path"
            ]
          },
          "split": {
            "type": "boolean"
          },
          "rule": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "kind",
          "id",
          "actor",
          "label",
          "day",
          "amount_clp",
          "applied_at",
          "movement",
          "document",
          "target",
          "reasons",
          "evidence",
          "own_transfer",
          "account",
          "split",
          "rule",
          "reviewed_at",
          "reviewed_by"
        ]
      },
      "FinanzasAutomaticActions": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "total_clp": {
            "type": "integer"
          },
          "unreviewed_count": {
            "type": "integer"
          },
          "by_actor": {
            "type": "object",
            "properties": {
              "rule": {
                "type": "integer"
              },
              "match": {
                "type": "integer"
              },
              "ai": {
                "type": "integer"
              }
            },
            "required": [
              "rule",
              "match",
              "ai"
            ]
          },
          "contraparte_merges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasContraparteMergeView"
            },
            "description": "Contrapartes Vitrina (or a person) merged this month, newest first: «Contrapartes unidas» with «Deshacer» (`undoable`) in Hecho por Vitrina."
          },
          "rules_created": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "label": {
                  "type": "string"
                },
                "contraparte": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "account": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "path": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "path"
                  ]
                },
                "status": {
                  "type": "string"
                },
                "created_at": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "label",
                "contraparte",
                "account",
                "status",
                "created_at"
              ]
            }
          },
          "groups": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "contraparte": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ]
                },
                "name": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                },
                "total_clp": {
                  "type": "integer"
                },
                "unreviewed_count": {
                  "type": "integer"
                },
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/FinanzasAutomaticAction"
                  }
                },
                "items_truncated": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "contraparte",
                "name",
                "count",
                "total_clp",
                "unreviewed_count",
                "items",
                "items_truncated"
              ]
            }
          }
        },
        "required": [
          "month",
          "from",
          "to",
          "count",
          "total_clp",
          "unreviewed_count",
          "by_actor",
          "contraparte_merges",
          "rules_created",
          "groups"
        ]
      },
      "FinanzasAutomaticActionsReview": {
        "type": "object",
        "properties": {
          "reviewed": {
            "type": "integer"
          }
        },
        "required": [
          "reviewed"
        ]
      },
      "FinanzasAutomaticActionUndo": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "link",
              "classification"
            ],
            "description": "link = a reconciliation link (Finanzas 08); classification = a classification by a rule or the IA (Finanzas 07)."
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "actor": {
            "type": "string",
            "enum": [
              "rule",
              "match",
              "ai"
            ],
            "description": "rule = Regla, match = Coincidencia, ai = IA."
          },
          "item": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "document",
                  "movement"
                ]
              },
              "id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "type",
              "id"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "teaching_signal": {
            "type": "boolean"
          }
        },
        "required": [
          "kind",
          "id",
          "actor",
          "item",
          "document_id",
          "teaching_signal"
        ]
      },
      "FinanzasOwnAccountSuggestion": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The account number as digits without leading zeros: the id for accept and dismiss."
          },
          "last4": {
            "type": "string"
          },
          "number_masked": {
            "type": "string",
            "description": "«•••• 4521»."
          },
          "bank_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The destination bank. The bank feeds do not report it for a recipient, so today it is null: show «Cuenta ··4521»."
          },
          "total_clp": {
            "type": "integer",
            "description": "Σ of the traspasos that went to this account (live ones only)."
          },
          "count": {
            "type": "integer"
          },
          "first_day": {
            "type": "string"
          },
          "last_day": {
            "type": "string"
          },
          "title": {
            "type": "string",
            "description": "«Agregar ··4521 como cuenta propia»."
          },
          "body": {
            "type": "string",
            "description": "«Le transferiste $9.950.000 a tu propio RUT. Si la cuenta es tuya, esos traspasos no cuentan como gasto.»"
          }
        },
        "required": [
          "key",
          "last4",
          "number_masked",
          "bank_name",
          "total_clp",
          "count",
          "first_day",
          "last_day",
          "title",
          "body"
        ]
      },
      "FinanzasOwnAccountSuggestions": {
        "type": "object",
        "properties": {
          "suggestions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasOwnAccountSuggestion"
            }
          },
          "count": {
            "type": "integer"
          }
        },
        "required": [
          "suggestions",
          "count"
        ]
      },
      "FinanzasOwnAccountAcceptBody": {
        "type": "object",
        "properties": {
          "institution_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120,
            "description": "The bank, when the person knows it. Optional: one click is enough."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120,
            "description": "Default «Cuenta ··‹últimos 4›»."
          }
        },
        "additionalProperties": false
      },
      "FinanzasOwnAccountDismiss": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "dismissed": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "key",
          "dismissed"
        ]
      },
      "FinanzasMonthSummary": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "description": "«septiembre 2026»."
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "ready_to_close",
              "closed",
              "closed_imported"
            ],
            "description": "open = abierto (cases touch it, or it has not ended); ready_to_close = listo para cerrar; closed = cerrado por una persona; closed_imported = «Cerrado antes de Vitrina» (the import closed it: it raises no cases, a change asks for a reason, there is nothing to close)."
          },
          "ended": {
            "type": "boolean"
          },
          "open_cases": {
            "type": "integer",
            "description": "Open «Por resolver» cases of the month (the month of each case’s oldest item): the one count of open work."
          },
          "open_cases_amount_clp": {
            "type": "integer",
            "description": "The money at stake in those cases, whole CLP: the same amount as «Por resolver» with `month`."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "closed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "closed_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "person",
              "chipax"
            ],
            "description": "Who closed the month: `person` («Cerrar mes») or `chipax` (the import: «Cerrado antes de Vitrina»). Null while open."
          },
          "imported_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the import closed it. Null unless `closed_source` is `chipax`."
          },
          "adjustment_count": {
            "type": "integer",
            "description": "Ajustes posteriores al cierre logged so far."
          }
        },
        "required": [
          "month",
          "label",
          "state",
          "ended",
          "open_cases",
          "open_cases_amount_clp",
          "closed_at",
          "closed_by",
          "closed_source",
          "imported_at",
          "adjustment_count"
        ]
      },
      "FinanzasMonths": {
        "type": "object",
        "properties": {
          "current_month": {
            "type": "string"
          },
          "months": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasMonthSummary"
            }
          }
        },
        "required": [
          "current_month",
          "months"
        ]
      },
      "FinanzasMonthStatus": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "description": "«septiembre 2026»."
          },
          "state": {
            "type": "string",
            "enum": [
              "open",
              "ready_to_close",
              "closed",
              "closed_imported"
            ],
            "description": "open = abierto (cases touch it, or it has not ended); ready_to_close = listo para cerrar; closed = cerrado por una persona; closed_imported = «Cerrado antes de Vitrina» (the import closed it: it raises no cases, a change asks for a reason, there is nothing to close)."
          },
          "ended": {
            "type": "boolean"
          },
          "open_cases": {
            "type": "integer",
            "description": "Open «Por resolver» cases of the month (the month of each case’s oldest item): the one count of open work."
          },
          "open_cases_amount_clp": {
            "type": "integer",
            "description": "The money at stake in those cases, whole CLP: the same amount as «Por resolver» with `month`."
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "closed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "closed_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "person",
              "chipax"
            ],
            "description": "Who closed the month: `person` («Cerrar mes») or `chipax` (the import: «Cerrado antes de Vitrina»). Null while open."
          },
          "imported_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the import closed it. Null unless `closed_source` is `chipax`."
          },
          "adjustment_count": {
            "type": "integer",
            "description": "Ajustes posteriores al cierre logged so far."
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "closed_by_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "can_close": {
            "type": "boolean"
          },
          "blocking_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "open_cases",
              "not_ended",
              "already_closed"
            ]
          },
          "checklist": {
            "type": "object",
            "properties": {
              "open_cases": {
                "type": "integer",
                "description": "The headline of «Antes de cerrar» (Finanzas 47): the month’s open Casos. Movements and documents are their detail, never a separate count."
              },
              "open_cases_amount_clp": {
                "type": "integer",
                "description": "The money at stake in those cases, whole CLP (= «Por resolver» `?mes=`)."
              },
              "open_case_movements_count": {
                "type": "integer",
                "description": "Movements behind the month’s open cases."
              },
              "open_case_documents_count": {
                "type": "integer",
                "description": "Documents to classify behind the month’s open cases."
              },
              "unreviewed_count": {
                "type": "integer",
                "description": "Automatic actions of the month nobody reviewed in «Hecho por Vitrina». The close confirms this number."
              },
              "unclassified_count": {
                "type": "integer"
              },
              "unmatched_count": {
                "type": "integer",
                "description": "Still to reconcile in the month: bank movements not fully matched plus income documents (boletas, facturas) issued in the month still «Por conciliar» (the same definition as Resultado). Detail, not a headline: what counts as open work is `open_cases`."
              },
              "unmatched_movements_count": {
                "type": "integer",
                "description": "Bank movements of the month not fully matched."
              },
              "unmatched_documents_count": {
                "type": "integer",
                "description": "Income documents issued in the month still «Por conciliar»."
              },
              "unmatched_pending_clp": {
                "type": "integer",
                "description": "What those movements and documents still leave unmatched, whole CLP."
              },
              "unmatched_movements_pending_clp": {
                "type": "integer"
              },
              "unmatched_documents_pending_clp": {
                "type": "integer"
              }
            },
            "required": [
              "open_cases",
              "open_cases_amount_clp",
              "open_case_movements_count",
              "open_case_documents_count",
              "unreviewed_count",
              "unclassified_count",
              "unmatched_count",
              "unmatched_movements_count",
              "unmatched_documents_count",
              "unmatched_pending_clp",
              "unmatched_movements_pending_clp",
              "unmatched_documents_pending_clp"
            ]
          },
          "close": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "unreviewed_count": {
                "type": "integer"
              },
              "unclassified_count": {
                "type": "integer"
              },
              "unmatched_count": {
                "type": "integer"
              },
              "snapshot_version": {
                "type": "integer"
              },
              "snapshot": {
                "type": "object",
                "properties": {
                  "documents": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "total_clp": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "count",
                        "total_clp"
                      ]
                    }
                  },
                  "movements": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": "integer"
                        },
                        "total_clp": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "count",
                        "total_clp"
                      ]
                    }
                  },
                  "accounts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "account_id": {
                          "type": "string"
                        },
                        "count": {
                          "type": "integer"
                        },
                        "total_clp": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "account_id",
                        "count",
                        "total_clp"
                      ]
                    }
                  },
                  "unclassified_documents": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "count"
                    ]
                  },
                  "unmatched_movements": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": "integer"
                      },
                      "pending_clp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "count",
                      "pending_clp"
                    ]
                  },
                  "unmatched_documents": {
                    "type": "object",
                    "properties": {
                      "count": {
                        "type": "integer"
                      },
                      "pending_clp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "count",
                      "pending_clp"
                    ],
                    "description": "Income documents still «Por conciliar» at the close (closes made after 2026-10-31)."
                  }
                },
                "required": [
                  "documents",
                  "movements",
                  "accounts",
                  "unclassified_documents",
                  "unmatched_movements"
                ],
                "description": "The month as it was closed (v1): documents by kind, movements by direction, classified totals per account (lines whose accrual month is this one), unclassified and unmatched."
              }
            },
            "required": [
              "unreviewed_count",
              "unclassified_count",
              "unmatched_count",
              "snapshot_version",
              "snapshot"
            ]
          },
          "changes_since_close": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "adjustments": {
                "type": "integer"
              },
              "documents_added": {
                "type": "integer"
              },
              "documents_voided": {
                "type": "integer"
              },
              "movements_added": {
                "type": "integer"
              }
            },
            "required": [
              "adjustments",
              "documents_added",
              "documents_voided",
              "movements_added"
            ]
          }
        },
        "required": [
          "month",
          "label",
          "state",
          "ended",
          "open_cases",
          "open_cases_amount_clp",
          "closed_at",
          "closed_by",
          "closed_source",
          "imported_at",
          "adjustment_count",
          "from",
          "to",
          "closed_by_name",
          "can_close",
          "blocking_reason",
          "checklist",
          "close",
          "changes_since_close"
        ]
      },
      "FinanzasPeriodAdjustment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "month": {
            "type": "string"
          },
          "change": {
            "type": "string",
            "enum": [
              "classify",
              "reclassify",
              "unclassify",
              "link",
              "unlink",
              "accept_without_document",
              "undo_accept_without_document",
              "add_document",
              "edit_document",
              "remove_document"
            ]
          },
          "label": {
            "type": "string",
            "description": "One Spanish line."
          },
          "actor": {
            "type": "string",
            "enum": [
              "person",
              "rule",
              "match",
              "ai",
              "import"
            ],
            "description": "person = a person (with `reason`); rule = Regla, match = Coincidencia, ai = IA, import = importación (no reason)."
          },
          "actor_user_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "actor_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "item": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "document",
                  "movement"
                ]
              },
              "id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "type",
              "id"
            ]
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kind": {
                "type": "string"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "issue_date": {
                "type": "string"
              },
              "total_clp": {
                "type": "integer"
              },
              "counterparty_name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "dte_type",
              "folio",
              "issue_date",
              "total_clp",
              "counterparty_name"
            ]
          },
          "movement": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "day": {
                "type": "string"
              },
              "direction": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "inbound",
                  "outbound"
                ]
              },
              "amount_clp": {
                "type": "integer"
              },
              "description": {
                "type": "string"
              }
            },
            "required": [
              "day",
              "direction",
              "amount_clp",
              "description"
            ]
          },
          "before": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "account": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "path": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "path"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "period": {
                  "type": "string"
                }
              },
              "required": [
                "account",
                "amount_clp",
                "period"
              ]
            }
          },
          "after": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "account": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "path": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "path"
                  ]
                },
                "amount_clp": {
                  "type": "integer"
                },
                "period": {
                  "type": "string"
                }
              },
              "required": [
                "account",
                "amount_clp",
                "period"
              ]
            }
          },
          "amount_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "A link: the amount linked or unlinked."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "month",
          "change",
          "label",
          "actor",
          "actor_user_id",
          "actor_name",
          "reason",
          "item",
          "document",
          "movement",
          "before",
          "after",
          "amount_clp",
          "created_at"
        ]
      },
      "FinanzasPeriodAdjustments": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "closed": {
            "type": "boolean"
          },
          "closed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasPeriodAdjustment"
            }
          }
        },
        "required": [
          "month",
          "closed",
          "closed_at",
          "total",
          "items"
        ]
      },
      "FinanzasChipaxReport": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "phase": {
            "type": "string",
            "enum": [
              "reading",
              "planning",
              "writing",
              "done"
            ]
          },
          "dry_run": {
            "type": "boolean"
          },
          "range": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "company_rut": {
            "type": "object",
            "properties": {
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "source": {
                "type": "string",
                "enum": [
                  "sii",
                  "chipax",
                  "none"
                ]
              }
            },
            "required": [
              "rut",
              "source"
            ],
            "description": "The workspace's own RUT the run writes as (issuer of its sales, receiver of its purchases) and its source: sii = the SII connection, chipax = the company Chipax's login names (no SII connection yet), none = neither (a dry run reports it; an import refuses with 409 company_rut_unknown). An SII RUT that differs from Chipax's fails the run (chipax_company_mismatch). Absent on runs from before 2026-11-02."
          },
          "read": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Rows read from Chipax per endpoint."
          },
          "accounts": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "chipax_account": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "chipax_path": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "ingreso",
                        "costo",
                        "gasto",
                        "no_operacional"
                      ]
                    },
                    "action": {
                      "type": "string",
                      "enum": [
                        "map",
                        "create",
                        "skip"
                      ],
                      "description": "map = file it under an existing Vitrina account (`account_id`); create = recreate it in the plan de cuentas (same name and tree); skip = do not import what Chipax classified there."
                    },
                    "account_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "account_path": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "chipax_parent_account": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "basis": {
                      "type": "string",
                      "enum": [
                        "previous_import",
                        "chosen",
                        "same_name",
                        "new",
                        "ambiguous"
                      ],
                      "description": "previous_import = mapped by an earlier run; chosen = the person chose it; same_name = matched to an existing Vitrina account of the same kind, by name or by the tree (`match` says how); new = will be created; ambiguous = several candidates (pick one in `account_map`)."
                    },
                    "match": {
                      "type": "string",
                      "enum": [
                        "previous",
                        "chosen",
                        "exact",
                        "normalized",
                        "similar",
                        "prefix",
                        "children",
                        "kept_distinct",
                        "none",
                        "ambiguous"
                      ],
                      "description": "How: exact = same name; normalized = same once case, accents, plurals, «de»/«y» and a «(…)» qualifier are folded; similar = one or two letters apart, under the account its Chipax parent maps to; prefix = one name starts the other, there too; children = its children already sit under that account; kept_distinct = another Chipax account claimed the same match, so this one is created rather than merged; none = nothing alike, created. Absent on runs before 2026-11-02."
                    },
                    "reason": {
                      "type": "string",
                      "description": "The decision in one sentence (Spanish), for the person reviewing the dry run. Absent on runs before 2026-11-02."
                    },
                    "candidates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "path": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "path"
                        ]
                      }
                    },
                    "retiros": {
                      "type": "boolean",
                      "description": "true = an owner-withdrawals account (name «Retiros…»): imported as non-operating onto the system «Retiros» account, shown below the resultado, never as a gasto. Absent on runs before 2026-11-08."
                    }
                  },
                  "required": [
                    "chipax_account",
                    "name",
                    "chipax_path",
                    "kind",
                    "action",
                    "account_id",
                    "account_path",
                    "chipax_parent_account",
                    "basis",
                    "candidates"
                  ]
                }
              },
              "retiros": {
                "type": "integer",
                "description": "Chipax accounts named «Retiros…» filed below the resultado as non-operating (not a gasto). Absent on runs before 2026-11-08."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "items"
            ]
          },
          "contrapartes": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons"
            ]
          },
          "documents": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "by_model": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "by_model"
            ]
          },
          "classifications": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "scaled": {
                "type": "integer",
                "description": "Documents whose Chipax amounts were spread over the document total (Chipax classified the net)."
              },
              "partial": {
                "type": "integer",
                "description": "Facturas Chipax classified only in part: Vitrina takes exactly the classified amounts and leaves the rest unclassified (it surfaces in Por resolver). Absent on runs before 2026-11-03."
              },
              "partial_items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "chipax_ref": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "total_clp": {
                      "type": "integer"
                    },
                    "unclassified_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "chipax_ref",
                    "label",
                    "total_clp",
                    "unclassified_clp"
                  ]
                }
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "scaled"
            ]
          },
          "credit_notes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chipax_ref": {
                  "type": "string"
                },
                "folio": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "day": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "path": {
                  "type": "string",
                  "enum": [
                    "summary",
                    "boleta_reference",
                    "none"
                  ]
                }
              },
              "required": [
                "chipax_ref",
                "folio",
                "day",
                "amount_clp",
                "path"
              ]
            },
            "description": "Every nota de crédito of sales and how it got its accounts: summary = Chipax hangs it on a monthly boleta summary (it takes the summary's income accounts); boleta_reference = it references a boleta (the summary of that month); none = neither, left unclassified («Ingresos sin clasificar»). Absent on runs before 2026-11-03."
          },
          "links": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons"
            ]
          },
          "movement_classifications": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons"
            ]
          },
          "rules": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "rut": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "document_kind": {
                      "type": "string"
                    },
                    "chipax_account": {
                      "type": "integer"
                    },
                    "account_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "documents": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "rut",
                    "name",
                    "document_kind",
                    "chipax_account",
                    "account_id",
                    "documents"
                  ]
                }
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "items"
            ]
          },
          "payment_terms": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "rut": {
                      "type": "string"
                    },
                    "days": {
                      "type": "integer"
                    },
                    "documents": {
                      "type": "integer"
                    },
                    "origin": {
                      "type": "string",
                      "enum": [
                        "chipax",
                        "inferred"
                      ],
                      "description": "chipax = the supplier's own term in Chipax; inferred = from the due dates of its invoices."
                    }
                  },
                  "required": [
                    "rut",
                    "days",
                    "documents"
                  ]
                }
              },
              "defaults": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "class": {
                      "type": "string",
                      "enum": [
                        "suppliers",
                        "customers",
                        "honorarios",
                        "expenses"
                      ]
                    },
                    "days": {
                      "type": "integer"
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "False when Vitrina already holds that number, or a person set another one (never overwritten)."
                    }
                  },
                  "required": [
                    "class",
                    "days",
                    "changed"
                  ]
                },
                "description": "The company's default payment terms per class, as Chipax holds them."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "items"
            ]
          },
          "aliases": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons"
            ]
          },
          "bank_accounts": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "chipax_account": {
                      "type": "string",
                      "description": "Chipax's `cuenta_corriente_id`."
                    },
                    "name": {
                      "type": "string"
                    },
                    "institution": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "number_tail": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "cuenta_corriente",
                        "cuenta_vista",
                        "tarjeta_credito",
                        "linea_credito",
                        "mercado_pago",
                        "otra"
                      ]
                    },
                    "currency": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "CLP",
                        "USD"
                      ]
                    },
                    "action": {
                      "type": "string",
                      "enum": [
                        "map",
                        "create",
                        "skip"
                      ],
                      "description": "map = the Vitrina account with the same number and currency; create = a new manual account; skip = not imported (currency_unsupported)."
                    },
                    "account_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "reason": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "chipax_account",
                    "name",
                    "institution",
                    "number_tail",
                    "type",
                    "currency",
                    "action",
                    "account_id",
                    "reason"
                  ]
                }
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons",
              "items"
            ],
            "description": "Chipax's bank accounts (/cuentas-corrientes) and the Vitrina account each one is. Absent on runs before 2026-11-02."
          },
          "movements": {
            "type": "object",
            "properties": {
              "create": {
                "type": "integer",
                "description": "New in Vitrina (dry run: would be created)."
              },
              "match": {
                "type": "integer",
                "description": "Already in Vitrina (e.g. from the SII): matched and enriched."
              },
              "already": {
                "type": "integer",
                "description": "Imported by an earlier run: nothing to do."
              },
              "skip": {
                "type": "integer"
              },
              "skip_reasons": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "Why: unmapped_account, ambiguous, no_categories, voided, classified_in_vitrina (a person already classified it: kept), company_rut_unknown (connect the SII first), movement_not_found (the bank movement is not in Vitrina: connect the bank or upload the cartola), movement_ambiguous, document_not_imported, direction_mismatch, nothing_open, movement_to_income, linked_to_another_contraparte, rut_is_another_contraparte, non_positive, chosen_skip; bank movements: no_bank_account (the line names no account at all; a line Chipax books on its «no account» bucket, cuenta_corriente_id 0, lands on a manual account «Chipax: movimientos sin cuenta bancaria» since 2026-11-03), bank_account_not_imported, currency_unsupported, unreadable, ingest_failed, usd_rate_missing (no dólar observado for the day: that line alone)."
              }
            },
            "required": [
              "create",
              "match",
              "already",
              "skip",
              "skip_reasons"
            ],
            "description": "Chipax's statement lines (/flujo-caja/cartolas) as bank movements, source chipax: create = new rows; match = the transfer was already in Vitrina from another rail (the bank's e-mail, a feed), kept as its twin, never a second row; already = an earlier run. Absent on runs before 2026-11-02."
          },
          "usd_movements": {
            "type": "object",
            "properties": {
              "lines": {
                "type": "integer"
              },
              "converted": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "usd_total": {
                "type": "integer",
                "description": "In cents."
              },
              "clp_total": {
                "type": "integer"
              },
              "rate_years": {
                "type": "object",
                "additionalProperties": {
                  "type": "string",
                  "enum": [
                    "cached",
                    "fetched",
                    "failed"
                  ]
                }
              },
              "missing_days": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "chipax_id": {
                      "type": "integer"
                    },
                    "day": {
                      "type": "string"
                    },
                    "usd": {
                      "type": "integer",
                      "description": "In cents."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "converted",
                        "failed",
                        "already"
                      ]
                    },
                    "rate_date": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The day of the dólar observado used: the line's day, or the last business day before it (weekend/holiday, at most 4 days back)."
                    },
                    "rate_clp": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "chipax_id",
                    "day",
                    "usd",
                    "status",
                    "rate_date",
                    "rate_clp",
                    "clp"
                  ]
                },
                "description": "Every USD line and the rate it used. Absent on runs before 2026-11-03."
              }
            },
            "required": [
              "lines",
              "converted",
              "failed",
              "usd_total",
              "clp_total",
              "rate_years",
              "missing_days"
            ],
            "description": "Chipax's USD lines (a dollar card) and their CLP at each day's dólar observado, a dry run included. Rule: the dólar observado of the line's day; a day with none published (weekend, holiday) takes the previous business day, at most 4 calendar days back; anything else fails that line alone (skip reason usd_rate_missing), never an older rate. The rate cache is filled first per year (mindicador, then the SII's yearly table). Absent when there are none, and on runs before 2026-11-03."
          },
          "chipax_wins": {
            "type": "object",
            "properties": {
              "links_undone": {
                "type": "integer"
              },
              "classifications_removed": {
                "type": "integer"
              },
              "contrapartes_merged": {
                "type": "integer"
              }
            },
            "required": [
              "links_undone",
              "classifications_removed",
              "contrapartes_merged"
            ],
            "description": "Chipax's people win over Vitrina's automatic work: links_undone = links the matcher, a rule or the IA made that Chipax reconciles differently, undone by the system so Chipax's go in; classifications_removed = movements whose IA / rule classification was removed because Chipax reconciles them to a document (it would count twice) or classifies them itself; contrapartes_merged = Contrapartes made from payments to a RUT that Chipax says paid one supplier, merged into it (the RUT becomes its alias). A person's work is never touched. A dry run counts what the import would do. Absent on runs before 2026-11-03."
          },
          "unmapped": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "account",
                    "document",
                    "link",
                    "classification"
                  ]
                },
                "chipax_ref": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                }
              },
              "required": [
                "type",
                "chipax_ref",
                "label",
                "reason"
              ]
            }
          },
          "totals_check": {
            "type": "object",
            "properties": {
              "available": {
                "type": "boolean"
              },
              "mismatches": {
                "type": "integer",
                "description": "Rows with a difference no reason covers."
              },
              "explained": {
                "type": "integer",
                "description": "Rows whose whole difference has a reason. Absent on runs before 2026-11-03."
              },
              "explained_by_code": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                },
                "description": "The explained pesos per reason over every row. Absent on runs before 2026-11-03."
              },
              "rows": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "chipax_account": {
                      "type": "integer"
                    },
                    "account_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "month": {
                      "type": "string"
                    },
                    "chipax_clp": {
                      "type": "integer",
                      "description": "Chipax's Resultado Operacional for the account and month."
                    },
                    "imported_chipax_clp": {
                      "type": "integer",
                      "description": "What Chipax classified on the documents now classified in Vitrina."
                    },
                    "vitrina_clp": {
                      "type": "integer",
                      "description": "What Vitrina will book in that account and month for those documents, at the Resultado basis: net of IVA, a nota de crédito subtracts (runs before 2026-11-03: the lines on the document total)."
                    },
                    "difference_clp": {
                      "type": "integer",
                      "description": "vitrina_clp − chipax_clp. Absent on runs before 2026-11-03."
                    },
                    "explanations": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "code": {
                            "type": "string",
                            "enum": [
                              "boletas_sin_nota_de_venta",
                              "nota_de_credito_de_boletas",
                              "redondeo"
                            ]
                          },
                          "amount_clp": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "code",
                          "amount_clp"
                        ]
                      },
                      "description": "The explained part of the difference, per reason: boletas_sin_nota_de_venta (a boleta summary books the month's boletas; Chipax's RO counts only its notas de venta), nota_de_credito_de_boletas (a nota de crédito of boletas subtracts in Vitrina; Chipax's RO leaves it out), redondeo (a split line prorated to the net books at most ±1 peso off Chipax). Absent on runs before 2026-11-03."
                    },
                    "unexplained_clp": {
                      "type": "integer",
                      "description": "The part of the difference no reason covers. Absent on runs before 2026-11-03."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "match",
                        "explained",
                        "mismatch"
                      ],
                      "description": "match = no difference; explained = every peso of the difference has a reason; mismatch = some does not. Absent on runs before 2026-11-03."
                    },
                    "matches": {
                      "type": "boolean",
                      "description": "No unexplained peso (status is not mismatch)."
                    }
                  },
                  "required": [
                    "chipax_account",
                    "account_id",
                    "month",
                    "chipax_clp",
                    "imported_chipax_clp",
                    "vitrina_clp",
                    "matches"
                  ]
                }
              },
              "excluded_balance_rows": {
                "type": "integer",
                "description": "Account × month rows left out of the check: balance accounts (activo, pasivo, patrimonio), which Chipax's Resultado Operacional does not cover. Absent on runs before 2026-11-02."
              }
            },
            "required": [
              "available",
              "mismatches",
              "rows"
            ]
          },
          "not_in_api": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "phase",
          "dry_run",
          "range",
          "read",
          "accounts",
          "contrapartes",
          "documents",
          "classifications",
          "links",
          "movement_classifications",
          "rules",
          "payment_terms",
          "aliases",
          "unmapped",
          "totals_check",
          "not_in_api",
          "warnings"
        ],
        "description": "Null in lists; on GET by id, the report (partial while running)."
      },
      "FinanzasChipaxImport": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "mode": {
            "type": "string",
            "enum": [
              "dry_run",
              "import"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "done",
              "failed"
            ]
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "account_map": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "chipax_account": {
                  "type": "integer"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "map",
                    "create",
                    "skip"
                  ],
                  "description": "map = file it under an existing Vitrina account (`account_id`); create = recreate it in the plan de cuentas (same name and tree); skip = do not import what Chipax classified there."
                },
                "account_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "chipax_account",
                "action"
              ]
            }
          },
          "requested_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 timestamp."
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 timestamp."
          },
          "created_at": {
            "type": "string",
            "description": "ISO 8601 timestamp."
          },
          "report": {
            "$ref": "#/components/schemas/FinanzasChipaxReport"
          }
        },
        "required": [
          "id",
          "mode",
          "status",
          "from",
          "to",
          "account_map",
          "requested_by",
          "error",
          "started_at",
          "finished_at",
          "created_at",
          "report"
        ]
      },
      "FinanzasChipaxStatus": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "app_id_preview": {
            "type": [
              "string",
              "null"
            ],
            "description": "A masked hint of the App ID («abc…xyz»). The secret is never returned."
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 timestamp."
          },
          "active_import": {
            "$ref": "#/components/schemas/FinanzasChipaxImport"
          },
          "last_import": {
            "$ref": "#/components/schemas/FinanzasChipaxImport"
          }
        },
        "required": [
          "connected",
          "app_id_preview",
          "verified_at",
          "active_import",
          "last_import"
        ]
      },
      "FinanzasAiSettings": {
        "type": "object",
        "properties": {
          "auto_apply": {
            "type": "boolean",
            "description": "«Aplicar sugerencias de IA automáticamente». true (default): a confident IA answer is applied on its own, marked IA and undoable in Hecho por Vitrina. false: IA answers only become Por resolver proposals."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a person last changed it; null = never (default on)."
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "auto_apply",
          "updated_at",
          "updated_by"
        ]
      },
      "FinanzasAiMetrics": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string"
          },
          "calls": {
            "type": "integer",
            "description": "IA calls in the month (one open item each)."
          },
          "auto_applied": {
            "type": "integer",
            "description": "Applied on their own (IA in Hecho por Vitrina)."
          },
          "proposed": {
            "type": "integer",
            "description": "Shown as the Por resolver proposal."
          },
          "no_answer": {
            "type": "integer",
            "description": "No usable answer (the item stays for a person)."
          },
          "undone": {
            "type": "integer",
            "description": "Auto-applied ones a person undid since."
          },
          "undo_rate": {
            "type": [
              "number",
              "null"
            ],
            "description": "undone / auto_applied, 0..1; null when nothing was applied."
          },
          "input_tokens": {
            "type": "integer"
          }
        },
        "required": [
          "month",
          "calls",
          "auto_applied",
          "proposed",
          "no_answer",
          "undone",
          "undo_rate",
          "input_tokens"
        ]
      },
      "FinanzasResultadoPostClose": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "closed_at": {
            "type": "string"
          },
          "adjustment_count": {
            "type": "integer"
          },
          "changed": {
            "type": "boolean"
          },
          "result_delta_clp": {
            "type": "integer"
          },
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "account_id": {
                  "type": "string"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "path": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "at_close_clp": {
                  "type": "integer"
                },
                "now_clp": {
                  "type": "integer"
                },
                "delta_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "account_id",
                "name",
                "path",
                "at_close_clp",
                "now_clp",
                "delta_clp"
              ]
            }
          }
        },
        "required": [
          "closed_at",
          "adjustment_count",
          "changed",
          "result_delta_clp",
          "accounts"
        ]
      },
      "FinanzasResultadoRow": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "`ingresos`, `ingresos_sin_clasificar`, `costos`, `margen_bruto`, `gastos`, `egresos_sin_documento`, `egresos_sin_clasificar`, `resultado_operacional`, `retiros`, or `account:<uuid>`."
          },
          "type": {
            "type": "string",
            "enum": [
              "section",
              "account",
              "line",
              "subtotal"
            ]
          },
          "label": {
            "type": "string"
          },
          "parent_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "`ingresos`, `ingresos_sin_clasificar`, `costos`, `margen_bruto`, `gastos`, `egresos_sin_documento`, `egresos_sin_clasificar`, `resultado_operacional`, `retiros`, or `account:<uuid>`."
          },
          "depth": {
            "type": "integer"
          },
          "account": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "ingreso",
                  "costo",
                  "gasto"
                ]
              },
              "path": {
                "type": "string"
              },
              "archived": {
                "type": "boolean"
              },
              "has_children": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "kind",
              "path",
              "archived",
              "has_children"
            ]
          },
          "sign": {
            "anyOf": [
              {
                "type": "number",
                "enum": [
                  1
                ]
              },
              {
                "type": "number",
                "enum": [
                  -1
                ]
              },
              {
                "type": "number",
                "enum": [
                  0
                ]
              }
            ],
            "description": "+1 adds to the operating result, −1 subtracts, 0 = a subtotal or outside the result (Retiros)."
          },
          "values": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "One value per entry of `months`, same order. Whole CLP."
          },
          "total": {
            "type": "integer"
          },
          "counts": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer"
            }
          },
          "count_total": {
            "type": [
              "integer",
              "null"
            ]
          },
          "alert": {
            "type": "boolean",
            "description": "Show the alert badge: «Egresos sin documento» with items, «Retiros» with a withdrawal that has no document."
          },
          "margin_pct": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": [
                "number",
                "null"
              ]
            }
          },
          "margin_pct_total": {
            "type": [
              "number",
              "null"
            ]
          },
          "por_conciliar": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "values": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "One value per entry of `months`, same order. Whole CLP."
              },
              "total": {
                "type": "integer"
              },
              "counts": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "count_total": {
                "type": "integer"
              }
            },
            "required": [
              "values",
              "total",
              "counts",
              "count_total"
            ],
            "description": "On `ingresos` only: documented income of the month not yet matched to money received, at document TOTALS (it is about money received; every other figure is net)."
          },
          "undocumented": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "values": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "One value per entry of `months`, same order. Whole CLP."
              },
              "total": {
                "type": "integer"
              },
              "counts": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "count_total": {
                "type": "integer"
              }
            },
            "required": [
              "values",
              "total",
              "counts",
              "count_total"
            ],
            "description": "On `retiros` only: the withdrawals with no document."
          },
          "by_account": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "account_id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "total": {
                  "type": "integer"
                }
              },
              "required": [
                "account_id",
                "name",
                "path",
                "kind",
                "total"
              ]
            }
          },
          "drillable": {
            "type": "boolean"
          }
        },
        "required": [
          "key",
          "type",
          "label",
          "parent_key",
          "depth",
          "account",
          "sign",
          "values",
          "total",
          "counts",
          "count_total",
          "alert",
          "margin_pct",
          "margin_pct_total",
          "por_conciliar",
          "undocumented",
          "by_account",
          "drillable"
        ]
      },
      "FinanzasResultado": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "current_month": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP"
            ]
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "proforma": {
                  "type": "boolean"
                },
                "closed": {
                  "type": "boolean"
                },
                "post_close": {
                  "$ref": "#/components/schemas/FinanzasResultadoPostClose"
                },
                "incompleto": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "reason": {
                      "type": "string",
                      "enum": [
                        "dentist_cost_missing",
                        "recurring_cost_missing"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Spanish, ready for the tooltip."
                    },
                    "account_name": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The missing recurring account (`recurring_cost_missing`), else null."
                    }
                  },
                  "required": [
                    "reason",
                    "message",
                    "account_name"
                  ],
                  "description": "«Mes incompleto»: a closed month whose dentist cost is $0 or that misses a large recurring cost. It never counts for an average, the `equilibrio` or the `hora_sillon.base`; Resultado and Costos flag the same months. Null otherwise; a proforma month is never tagged."
                }
              },
              "required": [
                "month",
                "label",
                "proforma",
                "closed",
                "post_close",
                "incompleto"
              ]
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasResultadoRow"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "resultado_operacional_clp": {
                "type": "integer"
              },
              "ingresos_clp": {
                "type": "integer"
              },
              "por_conciliar_clp": {
                "type": "integer"
              },
              "egresos_sin_documento": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "total_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "count",
                  "total_clp"
                ]
              },
              "retiros_clp": {
                "type": "integer"
              },
              "retiros_sin_documento": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "total_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "count",
                  "total_clp"
                ]
              }
            },
            "required": [
              "resultado_operacional_clp",
              "ingresos_clp",
              "por_conciliar_clp",
              "egresos_sin_documento",
              "retiros_clp",
              "retiros_sin_documento"
            ]
          },
          "waterfall": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "ingresos",
                    "costos",
                    "gastos",
                    "egresos_sin_documento",
                    "egresos_sin_clasificar",
                    "resultado_operacional"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "running_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "key",
                "label",
                "amount_clp",
                "running_clp"
              ]
            }
          }
        },
        "required": [
          "from",
          "to",
          "current_month",
          "currency",
          "months",
          "rows",
          "summary",
          "waterfall"
        ]
      },
      "FinanzasResultadoItems": {
        "type": "object",
        "properties": {
          "row": {
            "type": "string"
          },
          "account": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name",
              "path"
            ]
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "sum_clp": {
            "type": "integer",
            "description": "Equals the cell (net)."
          },
          "gross_sum_clp": {
            "type": "integer",
            "description": "The same items at document totals."
          },
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string",
                  "enum": [
                    "document",
                    "movement"
                  ]
                },
                "id": {
                  "type": "string"
                },
                "day": {
                  "type": "string"
                },
                "periods": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "amount_clp": {
                  "type": "integer",
                  "description": "Signed NET contribution of this item to the line (what the cell sums; por_conciliar: what it still waits for, at totals)."
                },
                "gross_amount_clp": {
                  "type": "integer",
                  "description": "The same contribution at document totals (classification lines as stored)."
                },
                "counterparty_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "account_ids": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "accounts": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "name": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "id",
                      "name",
                      "path"
                    ]
                  }
                },
                "documented": {
                  "type": "boolean"
                },
                "accepted_without_document": {
                  "type": "boolean"
                },
                "document": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "kind": {
                      "type": "string"
                    },
                    "dte_type": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "folio": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "total_clp": {
                      "type": "integer"
                    },
                    "result_clp": {
                      "type": "integer",
                      "description": "What the whole document counts for in the resultado: neto + exento (sales), total minus the recoverable IVA and specific taxes (purchases with crédito fiscal, so IVA no recuperable counts), the total otherwise."
                    },
                    "payment_state": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "kind",
                    "dte_type",
                    "folio",
                    "total_clp",
                    "result_clp",
                    "payment_state"
                  ]
                },
                "movement": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "direction": {
                      "type": "string",
                      "enum": [
                        "inbound",
                        "outbound"
                      ]
                    },
                    "amount_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "direction",
                    "amount_clp"
                  ]
                }
              },
              "required": [
                "type",
                "id",
                "day",
                "periods",
                "amount_clp",
                "gross_amount_clp",
                "counterparty_name",
                "description",
                "account_ids",
                "accounts",
                "documented",
                "accepted_without_document",
                "document",
                "movement"
              ]
            }
          }
        },
        "required": [
          "row",
          "account",
          "from",
          "to",
          "total",
          "sum_clp",
          "gross_sum_clp",
          "items"
        ]
      },
      "FinanzasCashFlowItem": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Stable item key: document:<uuid>, recurring:<uuid>:<YYYY-MM-DD>, retention:<YYYY-MM>, rescue:<uuid>, cuotas:<day>, appointments:<day>, cash_pending."
          },
          "source": {
            "type": "string",
            "enum": [
              "payable",
              "receivable",
              "retention",
              "recurring",
              "cuotas",
              "appointments",
              "cash_pending",
              "card_settlement",
              "rescue"
            ],
            "description": "payable = a purchase, honorario or gasto by due date; receivable = a sale or issued honorario by due date; retention = the retención of honorarios received, due with the F29; recurring = a recurring item occurrence; cuotas = patient cuotas of the day; appointments = booked appointments with a price of the day; cash_pending = efectivo por depositar (the one shared amount, `cash_pending`), spread over its expected deposit days (the next 5 business days), never all on today; card_settlement = card payments a processor has not paid out yet, on their payout day (2 business days, Chile holidays skipped); a transfer with no bank match is not projected (it is a Por resolver case); rescue = a planned «Rescatar fondo»."
          },
          "direction": {
            "type": "string",
            "enum": [
              "inflow",
              "outflow"
            ]
          },
          "label": {
            "type": "string"
          },
          "amount_clp": {
            "type": "integer"
          },
          "date": {
            "type": "string",
            "description": "Where the plan puts it."
          },
          "due_date": {
            "type": "string",
            "description": "Where its source puts it (due date, occurrence)."
          },
          "overdue": {
            "type": "boolean",
            "description": "An overdue payable. In the plan only when moved to a date; otherwise it is a «Vencida sin pago visto» (GET /finanzas/cash-flow/overdue-no-payment-seen)."
          },
          "movable": {
            "type": "boolean",
            "description": "«Mover pagos» applies (documents, recurring items, retención)."
          },
          "move": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "original_on": {
                "type": "string"
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "original_on",
              "note"
            ]
          },
          "document": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string"
              },
              "dte_type": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "folio": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "kind",
              "dte_type",
              "folio"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "recurring_item_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "rescue_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many it groups (cuotas, appointments, honorarios)."
          }
        },
        "required": [
          "key",
          "source",
          "direction",
          "label",
          "amount_clp",
          "date",
          "due_date",
          "overdue",
          "movable",
          "move",
          "document",
          "contraparte",
          "recurring_item_id",
          "rescue_id",
          "count"
        ],
        "description": "What «deja el saldo en …» names: the payment at which the confirmed balance first goes below zero; when it never does (or already starts below zero), the biggest payment of the lowest day."
      },
      "FinanzasCashFlowLowest": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "date": {
            "type": "string"
          },
          "balance_clp": {
            "type": "integer"
          },
          "negative": {
            "type": "boolean"
          },
          "is_today": {
            "type": "boolean",
            "description": "True when the lowest point is today (today's real balance, or a projected today lower than it): the screen says «Hoy es el punto más bajo». The balance is never above «Caja hoy»."
          },
          "cause": {
            "$ref": "#/components/schemas/FinanzasCashFlowItem"
          },
          "cause_balance_after_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The confirmed balance right after `cause` (null without a cause)."
          }
        },
        "required": [
          "date",
          "balance_clp",
          "negative",
          "is_today",
          "cause",
          "cause_balance_after_clp"
        ],
        "description": "The lowest projected balance in the range."
      },
      "FinanzasCashFlowOverdueNoPaymentSeen": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "amount_clp": {
            "type": "integer",
            "description": "What is still open on them (total − retención − links)."
          }
        },
        "required": [
          "count",
          "amount_clp"
        ],
        "description": "«Vencidas sin pago visto»: payables past their due date with no matching payment in the data and no date given with «Mover pagos». Vitrina does not claim they are unpaid, so they stay out of the projection and every headline. Review them with GET /finanzas/cash-flow/overdue-no-payment-seen."
      },
      "FinanzasInheritedOverdue": {
        "type": "object",
        "properties": {
          "count": {
            "type": "integer"
          },
          "amount_clp": {
            "type": "integer",
            "description": "Retired (Finanzas 50): always zero."
          }
        },
        "required": [
          "count",
          "amount_clp"
        ],
        "description": "Retired (Finanzas 50): always zero. The payables it counted («Vencido heredado») are «Pagado antes de Vitrina» now, settled; list them with GET /finanzas/documents?paid_before_vitrina=true.",
        "deprecated": true
      },
      "FinanzasCashFlowRescue": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "planned_on": {
            "type": "string"
          },
          "amount_clp": {
            "type": "integer"
          },
          "source_name": {
            "type": "string"
          },
          "source_bank_account_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "planned_on",
          "amount_clp",
          "source_name",
          "source_bank_account_id",
          "note",
          "created_at"
        ]
      },
      "FinanzasCashFlow": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string",
            "description": "Today (America/Santiago)."
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "granularity": {
            "type": "string",
            "enum": [
              "day",
              "week"
            ]
          },
          "opening": {
            "type": "object",
            "properties": {
              "balance_clp": {
                "type": "integer",
                "description": "Today's balance of the operating accounts (every account except savings accounts)."
              },
              "as_of": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "accounts_count": {
                "type": "integer"
              },
              "unknown_count": {
                "type": "integer",
                "description": "Accounts without a known balance (left out)."
              },
              "savings": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "institution_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "balance_clp": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "institution_name",
                    "balance_clp"
                  ]
                }
              },
              "savings_clp": {
                "type": "integer"
              }
            },
            "required": [
              "balance_clp",
              "as_of",
              "accounts_count",
              "unknown_count",
              "savings",
              "savings_clp"
            ]
          },
          "periods": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "start": {
                  "type": "string"
                },
                "end": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "actual",
                    "projected",
                    "mixed"
                  ]
                },
                "inflow_clp": {
                  "type": "integer",
                  "description": "Actual: bank income. Projected: known income."
                },
                "typical_inflow_clp": {
                  "type": "integer",
                  "description": "Projected: typical weekday income above the known income."
                },
                "outflow_clp": {
                  "type": "integer"
                },
                "closing_balance_clp": {
                  "type": "integer",
                  "description": "The confirmed balance: bank balances plus known items only."
                },
                "expected_balance_clp": {
                  "type": "integer",
                  "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income (median of the last 90 days) of every projected day so far: the dashed line. Never a headline. Actual days: the real balance."
                },
                "band": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "low_clp": {
                      "type": "integer"
                    },
                    "high_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "low_clp",
                    "high_clp"
                  ],
                  "description": "Retired with the uncertainty band (#6993): always null. The dashed line is `expected_balance_clp`."
                },
                "items_count": {
                  "type": "integer"
                }
              },
              "required": [
                "start",
                "end",
                "kind",
                "inflow_clp",
                "typical_inflow_clp",
                "outflow_clp",
                "closing_balance_clp",
                "expected_balance_clp",
                "band",
                "items_count"
              ]
            }
          },
          "lowest": {
            "$ref": "#/components/schemas/FinanzasCashFlowLowest"
          },
          "month_end": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string"
              },
              "balance_clp": {
                "type": "integer",
                "description": "The confirmed balance."
              },
              "expected_balance_clp": {
                "type": "integer",
                "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income (median of the last 90 days) of every projected day so far: the dashed line. Never a headline. Actual days: the real balance."
              },
              "band": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "low_clp": {
                    "type": "integer"
                  },
                  "high_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "low_clp",
                  "high_clp"
                ],
                "description": "Retired with the uncertainty band (#6993): always null. The dashed line is `expected_balance_clp`."
              }
            },
            "required": [
              "date",
              "balance_clp",
              "expected_balance_clp",
              "band"
            ],
            "description": "The projected balance at the end of the current month."
          },
          "totals": {
            "type": "object",
            "properties": {
              "actual_inflow_clp": {
                "type": "integer"
              },
              "actual_outflow_clp": {
                "type": "integer"
              },
              "projected_inflow_clp": {
                "type": "integer"
              },
              "typical_inflow_clp": {
                "type": "integer"
              },
              "projected_outflow_clp": {
                "type": "integer"
              },
              "by_source": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "actual_inflow_clp",
              "actual_outflow_clp",
              "projected_inflow_clp",
              "typical_inflow_clp",
              "projected_outflow_clp",
              "by_source"
            ]
          },
          "overdue": {
            "type": "object",
            "properties": {
              "payables_clp": {
                "type": "integer",
                "description": "Payables overdue up to 90 days with no payment seen: out of the projection (part of `overdue_no_payment_seen`)."
              },
              "payables_count": {
                "type": "integer"
              },
              "stale_payables_clp": {
                "type": "integer"
              },
              "stale_payables_count": {
                "type": "integer"
              },
              "receivables_clp": {
                "type": "integer"
              },
              "receivables_count": {
                "type": "integer"
              }
            },
            "required": [
              "payables_clp",
              "payables_count",
              "stale_payables_clp",
              "stale_payables_count",
              "receivables_clp",
              "receivables_count"
            ]
          },
          "overdue_no_payment_seen": {
            "$ref": "#/components/schemas/FinanzasCashFlowOverdueNoPaymentSeen"
          },
          "inherited_overdue": {
            "$ref": "#/components/schemas/FinanzasInheritedOverdue"
          },
          "cash_pending": {
            "$ref": "#/components/schemas/FinanzasCashPendingSummary"
          },
          "typical_income": {
            "type": "object",
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "history",
                  "insufficient_history"
                ]
              },
              "weeks": {
                "type": "integer",
                "description": "Whole weeks of bank history behind the weekday band."
              }
            },
            "required": [
              "basis",
              "weeks"
            ],
            "description": "What the typical weekday income stands on. `insufficient_history` = fewer than 4 weeks of bank history: no typical income is projected (every `typical_inflow_clp` is 0 and there is no band) and the page should say so."
          },
          "band_basis": {
            "type": "object",
            "properties": {
              "from": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "to": {
                "type": "string"
              },
              "weekdays": {
                "type": "array",
                "items": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "weekday": {
                      "type": "integer",
                      "description": "0 = domingo."
                    },
                    "min_clp": {
                      "type": "integer"
                    },
                    "median_clp": {
                      "type": "integer"
                    },
                    "max_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "weekday",
                    "min_clp",
                    "median_clp",
                    "max_clp"
                  ]
                }
              }
            },
            "required": [
              "from",
              "to",
              "weekdays"
            ]
          },
          "rescues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasCashFlowRescue"
            }
          },
          "moves_count": {
            "type": "integer"
          }
        },
        "required": [
          "as_of",
          "from",
          "to",
          "granularity",
          "opening",
          "periods",
          "lowest",
          "month_end",
          "totals",
          "overdue",
          "overdue_no_payment_seen",
          "inherited_overdue",
          "cash_pending",
          "typical_income",
          "band_basis",
          "rescues",
          "moves_count"
        ]
      },
      "FinanzasCashFlowCalendar": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "opening_balance_clp": {
            "type": "integer"
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "opening_balance_clp": {
                  "type": "integer"
                },
                "items": {
                  "type": "array",
                  "items": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/FinanzasCashFlowItem"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "balance_after_clp": {
                            "type": "integer",
                            "description": "The expected balance right after this item."
                          }
                        },
                        "required": [
                          "balance_after_clp"
                        ]
                      }
                    ]
                  }
                },
                "typical_inflow_clp": {
                  "type": "integer"
                },
                "closing_balance_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "date",
                "opening_balance_clp",
                "items",
                "typical_inflow_clp",
                "closing_balance_clp"
              ]
            }
          },
          "lowest": {
            "$ref": "#/components/schemas/FinanzasCashFlowLowest"
          }
        },
        "required": [
          "as_of",
          "from",
          "to",
          "opening_balance_clp",
          "days",
          "lowest"
        ]
      },
      "FinanzasCashFlowMove": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Empty on a dry run."
          },
          "item_key": {
            "type": "string"
          },
          "original_on": {
            "type": "string"
          },
          "planned_on": {
            "type": "string"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "item_key",
          "original_on",
          "planned_on",
          "note",
          "updated_at"
        ],
        "description": "Null when the item went back to its own date."
      },
      "FinanzasCashFlowEffect": {
        "type": "object",
        "properties": {
          "before": {
            "type": "object",
            "properties": {
              "lowest": {
                "$ref": "#/components/schemas/FinanzasCashFlowLowest"
              },
              "month_end": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string"
                  },
                  "balance_clp": {
                    "type": "integer",
                    "description": "The confirmed balance."
                  },
                  "expected_balance_clp": {
                    "type": "integer",
                    "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income (median of the last 90 days) of every projected day so far: the dashed line. Never a headline. Actual days: the real balance."
                  },
                  "band": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "low_clp": {
                        "type": "integer"
                      },
                      "high_clp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "low_clp",
                      "high_clp"
                    ],
                    "description": "Retired with the uncertainty band (#6993): always null. The dashed line is `expected_balance_clp`."
                  }
                },
                "required": [
                  "date",
                  "balance_clp",
                  "expected_balance_clp",
                  "band"
                ]
              }
            },
            "required": [
              "lowest",
              "month_end"
            ]
          },
          "after": {
            "type": "object",
            "properties": {
              "lowest": {
                "$ref": "#/components/schemas/FinanzasCashFlowLowest"
              },
              "month_end": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string"
                  },
                  "balance_clp": {
                    "type": "integer",
                    "description": "The confirmed balance."
                  },
                  "expected_balance_clp": {
                    "type": "integer",
                    "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income (median of the last 90 days) of every projected day so far: the dashed line. Never a headline. Actual days: the real balance."
                  },
                  "band": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "low_clp": {
                        "type": "integer"
                      },
                      "high_clp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "low_clp",
                      "high_clp"
                    ],
                    "description": "Retired with the uncertainty band (#6993): always null. The dashed line is `expected_balance_clp`."
                  }
                },
                "required": [
                  "date",
                  "balance_clp",
                  "expected_balance_clp",
                  "band"
                ]
              }
            },
            "required": [
              "lowest",
              "month_end"
            ]
          },
          "horizon": {
            "type": "object",
            "properties": {
              "days": {
                "type": "integer"
              },
              "to": {
                "type": "string",
                "description": "Last day of the window."
              }
            },
            "required": [
              "days",
              "to"
            ],
            "description": "`lowest` is measured over today .. `to` (`horizon_days`, default 30: the page's window); `month_end` is the end of the current month."
          }
        },
        "required": [
          "before",
          "after",
          "horizon"
        ]
      },
      "FinanzasCashFlowMoveResult": {
        "type": "object",
        "properties": {
          "dry_run": {
            "type": "boolean"
          },
          "move": {
            "$ref": "#/components/schemas/FinanzasCashFlowMove"
          },
          "item": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string"
              },
              "label": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              },
              "due_date": {
                "type": "string"
              },
              "planned_on": {
                "type": "string"
              }
            },
            "required": [
              "key",
              "label",
              "amount_clp",
              "due_date",
              "planned_on"
            ]
          },
          "effect": {
            "$ref": "#/components/schemas/FinanzasCashFlowEffect"
          }
        },
        "required": [
          "dry_run",
          "move",
          "item",
          "effect"
        ]
      },
      "FinanzasCashFlowMoveBody": {
        "type": "object",
        "properties": {
          "item_key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The `key` of a movable item (document:<uuid>, recurring:<uuid>:<YYYY-MM-DD>, retention:<YYYY-MM>)."
          },
          "planned_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The new planned day, today to a year ahead. Its own date = back to it."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "required": [
          "item_key",
          "planned_on"
        ]
      },
      "FinanzasCashFlowMoveRemoved": {
        "type": "object",
        "properties": {
          "removed": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCashFlowMove"
              },
              {
                "type": "object"
              }
            ]
          },
          "effect": {
            "$ref": "#/components/schemas/FinanzasCashFlowEffect"
          }
        },
        "required": [
          "removed",
          "effect"
        ]
      },
      "FinanzasCashFlowRescueResult": {
        "type": "object",
        "properties": {
          "dry_run": {
            "type": "boolean"
          },
          "rescue": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCashFlowRescue"
              },
              {
                "type": [
                  "object",
                  "null"
                ],
                "description": "Null on a dry run."
              }
            ]
          },
          "effect": {
            "$ref": "#/components/schemas/FinanzasCashFlowEffect"
          }
        },
        "required": [
          "dry_run",
          "rescue",
          "effect"
        ]
      },
      "FinanzasCashFlowRescueBody": {
        "type": "object",
        "properties": {
          "planned_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Today to a year ahead."
          },
          "amount_clp": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000000
          },
          "source_name": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120,
            "description": "What it comes from («Fondo mutuo Itaú»). Defaults to the account name."
          },
          "source_bank_account_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A savings account (cuenta de ahorro) it comes from."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "required": [
          "planned_on",
          "amount_clp"
        ]
      },
      "FinanzasCashFlowRescueRemoved": {
        "type": "object",
        "properties": {
          "removed": {
            "$ref": "#/components/schemas/FinanzasCashFlowRescue"
          }
        },
        "required": [
          "removed"
        ]
      },
      "FinanzasRecurringItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "outflow",
              "inflow"
            ]
          },
          "amount_clp": {
            "type": "integer"
          },
          "cadence": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly",
              "yearly"
            ],
            "description": "weekly = every 7 days; biweekly = every 14 days (quincenal); monthly, quarterly and yearly keep the day of the month of `starts_on` (clamped to the month's last day)."
          },
          "starts_on": {
            "type": "string"
          },
          "ends_on": {
            "type": [
              "string",
              "null"
            ]
          },
          "account": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "kind": {
                "type": "string"
              },
              "archived": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "name",
              "kind",
              "archived"
            ]
          },
          "contraparte": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "string",
            "description": "«Mensual · $2.847.000»."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "scheduled",
              "ended"
            ]
          },
          "next_on": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "direction",
          "amount_clp",
          "cadence",
          "starts_on",
          "ends_on",
          "account",
          "contraparte",
          "note",
          "summary",
          "status",
          "next_on",
          "created_at",
          "updated_at"
        ]
      },
      "FinanzasRecurringItemBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "direction": {
            "type": "string",
            "enum": [
              "outflow",
              "inflow"
            ]
          },
          "amount_clp": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000000
          },
          "cadence": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly",
              "yearly"
            ],
            "description": "weekly = every 7 days; biweekly = every 14 days (quincenal); monthly, quarterly and yearly keep the day of the month of `starts_on` (clamped to the month's last day)."
          },
          "starts_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "ends_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The plan de cuentas account (Arriendo, Sueldos…)."
          },
          "contraparte_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional: a document of this Contraparte due near an occurrence stands for it (never counted twice)."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "required": [
          "name",
          "direction",
          "amount_clp",
          "cadence",
          "starts_on",
          "account_id"
        ]
      },
      "FinanzasRecurringItemPatch": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "direction": {
            "type": "string",
            "enum": [
              "outflow",
              "inflow"
            ]
          },
          "amount_clp": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000000000
          },
          "cadence": {
            "type": "string",
            "enum": [
              "weekly",
              "biweekly",
              "monthly",
              "quarterly",
              "yearly"
            ],
            "description": "weekly = every 7 days; biweekly = every 14 days (quincenal); monthly, quarterly and yearly keep the day of the month of `starts_on` (clamped to the month's last day)."
          },
          "starts_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "ends_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The plan de cuentas account (Arriendo, Sueldos…)."
          },
          "contraparte_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Optional: a document of this Contraparte due near an occurrence stands for it (never counted twice)."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "additionalProperties": false
      },
      "FinanzasRecurringItemEndBody": {
        "type": "object",
        "properties": {
          "ends_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The last day an occurrence may fall on. Default: today (the day before it starts, if it has not started)."
          }
        }
      },
      "FinanzasCashFlowOverdueNoPaymentSeenList": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "amount_clp": {
            "type": "integer"
          },
          "items": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/FinanzasCashFlowItem"
                },
                {
                  "type": "object"
                }
              ]
            },
            "description": "Newest due date first. `date` = `due_date`; each is movable («Mover pagos» puts it back in the plan on that day)."
          }
        },
        "required": [
          "as_of",
          "count",
          "amount_clp",
          "items"
        ]
      },
      "FinanzasCashFlowOverdueResolveResult": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid",
            "description": "Pass it to POST /finanzas/inherited-overdue/batches/{batchId}/undo («Deshacer»)."
          },
          "resolved": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "skipped": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "not_found",
                    "not_eligible"
                  ],
                  "description": "not_eligible: not an overdue open payable any more (paid, linked, voided, or someone got there first)."
                }
              },
              "required": [
                "id",
                "reason"
              ]
            }
          },
          "amount_clp": {
            "type": "integer",
            "description": "What was open on the documents marked paid."
          },
          "remaining": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCashFlowOverdueNoPaymentSeen"
              },
              {
                "description": "The «Vencidas sin pago visto» left after this batch."
              }
            ]
          }
        },
        "required": [
          "batch_id",
          "resolved",
          "skipped",
          "amount_clp",
          "remaining"
        ]
      },
      "FinanzasCashFlowOverdueResolveBody": {
        "type": "object",
        "properties": {
          "document_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "maxItems": 500
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          }
        },
        "required": [
          "document_ids"
        ]
      },
      "FinanzasAlert": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "cash_negative",
              "f29_due",
              "supplier_invoice_missing"
            ],
            "description": "`cash_negative`: the Flujo de caja projection drops below zero in the next 30 days. `f29_due`: an F29 with something to pay is due within 5 days and is unpaid. `supplier_invoice_missing`: a Contraparte that invoiced each of the last 3 months has no invoice this month, 3 days past its usual day."
          },
          "title": {
            "type": "string",
            "description": "One line, Spanish."
          },
          "body": {
            "type": "string",
            "description": "What happened and what to do."
          },
          "cta_label": {
            "type": "string",
            "description": "The button that fixes it."
          },
          "path": {
            "type": "string",
            "description": "Workspace-relative screen that fixes it (`/finanzas/reportes/flujo-de-caja`, `/finanzas/documentos?vista=f29`, `/finanzas/contrapartes/<id>`)."
          },
          "opened_on": {
            "type": "string",
            "description": "Day first seen (YYYY-MM-DD, Santiago)."
          },
          "notified_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the members were told; null until then."
          },
          "updated_at": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "additionalProperties": {},
            "description": "The facts, whole CLP. cash_negative: `lowest_date`, `lowest_balance_clp`, `today_balance_clp`, `cause_label`, `cause_amount_clp`, `horizon_days`. f29_due: `period`, `due_date`, `amount_clp`, `days_left`, `amount_is_final`, `document_id`. supplier_invoice_missing: `contraparte_id`, `contraparte_name`, `period`, `usual_day`, `typical_amount_clp`, `months_invoiced`."
          }
        },
        "required": [
          "id",
          "kind",
          "title",
          "body",
          "cta_label",
          "path",
          "opened_on",
          "notified_at",
          "updated_at",
          "data"
        ]
      },
      "FinanzasAlertList": {
        "type": "array",
        "items": {
          "$ref": "#/components/schemas/FinanzasAlert"
        }
      },
      "FinanzasMandatoSignature": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "phase": {
            "type": "string",
            "enum": [
              "none",
              "pending_signature",
              "connecting",
              "connected",
              "failed",
              "declined",
              "revoked"
            ],
            "description": "`pending_signature` = the representante legal has the link; `connecting` = signed, being registered at the SII («Firmado, conectando con el SII…»); `connected` = registered; `failed` = signed but the SII registration keeps failing (retried automatically); `declined` = the signer rejected; `revoked` = revoked in Bancos y SII."
          },
          "status": {
            "type": "string"
          },
          "terms_version": {
            "type": "string"
          },
          "terms_sha256": {
            "type": "string"
          },
          "signer": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "rut": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "rut",
              "email"
            ]
          },
          "signing_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The representante legal's signing link while the signature is pending; null afterwards."
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "attested_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "problem": {
            "type": [
              "string",
              "null"
            ]
          },
          "document_available": {
            "type": "boolean",
            "description": "The signed PDF can be downloaded (`GET …/sii/{id}/mandato/document`)."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "phase",
          "status",
          "terms_version",
          "terms_sha256",
          "signer",
          "signing_url",
          "signed_at",
          "attested_at",
          "problem",
          "document_available",
          "created_at"
        ]
      },
      "FinanzasDigestPreview": {
        "type": "object",
        "properties": {
          "week_start": {
            "type": "string",
            "description": "Monday of this week (YYYY-MM-DD, workspace time)."
          },
          "send_at": {
            "type": "string",
            "description": "This week's send moment: Monday 09:00 workspace time (ISO)."
          },
          "next_send_at": {
            "type": "string",
            "description": "Next Monday's 09:00 workspace time (ISO)."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "empty",
              "missed"
            ],
            "description": "This week's digest: `pending` (Monday's send window has not closed and nothing went out yet), `sent`, `empty` (no finance data, nothing went out) or `missed` (the window closed without a digest: the workspace had data but it did not go, e.g. it joined mid-week or the send failed; the next one is `next_send_at`)."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "will_send": {
            "type": "boolean",
            "description": "The workspace has finance data now; false = no digest would go out."
          },
          "channels": {
            "type": "object",
            "properties": {
              "email": {
                "type": "boolean"
              },
              "whatsapp": {
                "type": "boolean"
              }
            },
            "required": [
              "email",
              "whatsapp"
            ],
            "description": "The caller's own channels: their notification preferences for `finanzas.weekly_digest` and, for WhatsApp, the admin's opt-in `finanzas_whatsapp` rail with an approved digest template."
          },
          "email": {
            "type": "object",
            "properties": {
              "subject": {
                "type": "string"
              },
              "hero": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "«Caja hoy» or «Te necesita»."
                  },
                  "value": {
                    "type": "string",
                    "description": "THE big number, formatted."
                  },
                  "caption": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "label",
                  "value",
                  "caption"
                ]
              },
              "problem": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "link": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Workspace-relative screen (prefix `/w/<workspace>`)."
                      }
                    },
                    "required": [
                      "label",
                      "path"
                    ]
                  }
                },
                "required": [
                  "text",
                  "link"
                ],
                "description": "The one problem (the only red line): the cash runs out within 30 days, or a bank needs reconnecting. Null when there is none."
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The rest: the month against the punto de equilibrio, the open cases, what Vitrina ordered."
              },
              "overdue_unpaid": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "text": {
                    "type": "string"
                  },
                  "link": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string"
                      },
                      "path": {
                        "type": "string",
                        "description": "Workspace-relative screen (prefix `/w/<workspace>`)."
                      }
                    },
                    "required": [
                      "label",
                      "path"
                    ]
                  }
                },
                "required": [
                  "text",
                  "link"
                ],
                "description": "«$X en facturas vencidas sin pago visto» with a link to review them; they are not in the cash forecast. Null when there are none or the caller cannot read reports."
              },
              "primary": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Workspace-relative screen (prefix `/w/<workspace>`)."
                  }
                },
                "required": [
                  "label",
                  "path"
                ]
              },
              "secondary": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string"
                  },
                  "path": {
                    "type": "string",
                    "description": "Workspace-relative screen (prefix `/w/<workspace>`)."
                  }
                },
                "required": [
                  "label",
                  "path"
                ]
              }
            },
            "required": [
              "subject",
              "hero",
              "problem",
              "lines",
              "overdue_unpaid",
              "primary",
              "secondary"
            ]
          },
          "whatsapp_text": {
            "type": "string",
            "description": "The WhatsApp `{{resumen}}` for the caller."
          },
          "figures": {
            "type": "object",
            "properties": {
              "reports_visible": {
                "type": "boolean"
              },
              "cash_today_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "month_end_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "in_30_days_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "runs_out_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ingresos_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "resultado_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "equilibrio_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "open_cases": {
                "type": "integer"
              }
            },
            "required": [
              "reports_visible",
              "cash_today_clp",
              "month_end_clp",
              "in_30_days_clp",
              "runs_out_date",
              "ingresos_clp",
              "resultado_clp",
              "equilibrio_clp",
              "open_cases"
            ],
            "description": "The same figures as `GET /finanzas/resumen` (whole CLP). Cash and result are null without `finanzas_reports:read`."
          }
        },
        "required": [
          "week_start",
          "send_at",
          "next_send_at",
          "status",
          "sent_at",
          "will_send",
          "channels",
          "email",
          "whatsapp_text",
          "figures"
        ]
      },
      "FinanzasEquilibrioProduccion": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "equilibrio_clp": {
            "type": "integer",
            "description": "The average break-even converted to production terms: `equilibrio.equilibrio_clp` × `ratio`."
          },
          "ratio": {
            "type": "number",
            "description": "Producción ÷ ventas netas over `months` (sums, not a mean of ratios), three decimals."
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "ingresos_clp": {
                  "type": "integer"
                },
                "produccion_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "month",
                "label",
                "ingresos_clp",
                "produccion_clp"
              ]
            },
            "description": "The bar months that have both figures, oldest first (at most 3)."
          }
        },
        "required": [
          "equilibrio_clp",
          "ratio",
          "months"
        ]
      },
      "FinanzasCostos": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "current_month": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP"
            ]
          },
          "is_clinic": {
            "type": "boolean",
            "description": "Clinic workspaces get `dentist_share` and `hora_sillon`; others see break-even only (both null)."
          },
          "reference_month": {
            "type": [
              "string",
              "null"
            ],
            "description": "The last month of the range that is not proforma (the headline), or null."
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "proforma": {
                  "type": "boolean"
                },
                "incompleto": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "reason": {
                      "type": "string",
                      "enum": [
                        "dentist_cost_missing",
                        "recurring_cost_missing"
                      ]
                    },
                    "message": {
                      "type": "string",
                      "description": "Spanish, ready for the tooltip."
                    },
                    "account_name": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The missing recurring account (`recurring_cost_missing`), else null."
                    }
                  },
                  "required": [
                    "reason",
                    "message",
                    "account_name"
                  ],
                  "description": "«Mes incompleto»: a closed month whose dentist cost is $0 or that misses a large recurring cost. It never counts for an average, the `equilibrio` or the `hora_sillon.base`; Resultado and Costos flag the same months. Null otherwise; a proforma month is never tagged."
                },
                "fijos_faltantes_clp": {
                  "type": "integer",
                  "description": "Recurring fixed costs not booked yet in this closed month, at their usual amount (the sum of `fijos_faltantes`). Already inside `equilibrio_clp`; never inside `fijos_clp` or `resultado_clp`. 0 on a proforma month."
                },
                "fijos_faltantes": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "account_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "account_name": {
                        "type": "string"
                      },
                      "usual_clp": {
                        "type": "integer",
                        "description": "Median of the account’s last 3 non-zero months before the gap."
                      }
                    },
                    "required": [
                      "account_id",
                      "account_name",
                      "usual_clp"
                    ]
                  },
                  "description": "The accounts behind `fijos_faltantes_clp`, usual amount descending."
                },
                "ingresos_clp": {
                  "type": "integer",
                  "description": "The Resultado’s «Ingresos» (net)."
                },
                "variables_clp": {
                  "type": "integer",
                  "description": "Costs on accounts flagged variable (documented or not) + «Egresos sin clasificar»."
                },
                "sin_clasificar_clp": {
                  "type": "integer",
                  "description": "Part of `variables_clp` that is «Egresos sin clasificar»."
                },
                "fijos_clp": {
                  "type": "integer",
                  "description": "Costs on accounts flagged fijo."
                },
                "contribucion_clp": {
                  "type": "integer",
                  "description": "ingresos − variables."
                },
                "contribucion_pct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Percent, one decimal."
                },
                "equilibrio_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Punto de equilibrio: (fijos + fijos faltantes) ÷ margen de contribución %, the monthly income at which the result is zero. `fijos_clp` stays the books; the recurring fixed costs not booked yet enter here at their usual amount (month rows: `fijos_faltantes_clp`). Null when it cannot be reached (see `equilibrio_sin_calculo`)."
                },
                "equilibrio_sin_calculo": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "sin_ingresos",
                    "margen_no_positivo"
                  ]
                },
                "brecha_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "ingresos − equilibrio (positive: above break-even)."
                },
                "cobertura_pct": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "ingresos ÷ equilibrio, one decimal."
                },
                "resultado_clp": {
                  "type": "integer",
                  "description": "ingresos − variables − fijos = the Resultado operacional."
                }
              },
              "required": [
                "month",
                "label",
                "proforma",
                "incompleto",
                "fijos_faltantes_clp",
                "fijos_faltantes",
                "ingresos_clp",
                "variables_clp",
                "sin_clasificar_clp",
                "fijos_clp",
                "contribucion_clp",
                "contribucion_pct",
                "equilibrio_clp",
                "equilibrio_sin_calculo",
                "brecha_clp",
                "cobertura_pct",
                "resultado_clp"
              ]
            }
          },
          "equilibrio": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "equilibrio_clp": {
                "type": "integer",
                "description": "The average break-even of the last 3 complete months, «Mes incompleto» skipped."
              },
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "months": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "equilibrio_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "month",
                    "label",
                    "equilibrio_clp"
                  ]
                }
              },
              "fijos_faltantes_clp": {
                "type": "integer",
                "description": "Mean of the bar months’ `fijos_faltantes_clp`; already inside `equilibrio_clp`."
              },
              "fijos_faltantes_accounts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Distinct names of the accounts missing in any bar month, usual amount descending."
              }
            },
            "required": [
              "equilibrio_clp",
              "from",
              "to",
              "months",
              "fijos_faltantes_clp",
              "fijos_faltantes_accounts"
            ],
            "description": "The bar the current month is judged against; independent of the range asked for. Null when no complete month has a break-even."
          },
          "equilibrio_produccion": {
            "$ref": "#/components/schemas/FinanzasEquilibrioProduccion"
          },
          "period": {
            "type": "object",
            "properties": {
              "ingresos_clp": {
                "type": "integer",
                "description": "The Resultado’s «Ingresos» (net)."
              },
              "variables_clp": {
                "type": "integer",
                "description": "Costs on accounts flagged variable (documented or not) + «Egresos sin clasificar»."
              },
              "sin_clasificar_clp": {
                "type": "integer",
                "description": "Part of `variables_clp` that is «Egresos sin clasificar»."
              },
              "fijos_clp": {
                "type": "integer",
                "description": "Costs on accounts flagged fijo."
              },
              "contribucion_clp": {
                "type": "integer",
                "description": "ingresos − variables."
              },
              "contribucion_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal."
              },
              "equilibrio_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Punto de equilibrio: (fijos + fijos faltantes) ÷ margen de contribución %, the monthly income at which the result is zero. `fijos_clp` stays the books; the recurring fixed costs not booked yet enter here at their usual amount (month rows: `fijos_faltantes_clp`). Null when it cannot be reached (see `equilibrio_sin_calculo`)."
              },
              "equilibrio_sin_calculo": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "sin_ingresos",
                  "margen_no_positivo"
                ]
              },
              "brecha_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "ingresos − equilibrio (positive: above break-even)."
              },
              "cobertura_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "ingresos ÷ equilibrio, one decimal."
              },
              "resultado_clp": {
                "type": "integer",
                "description": "ingresos − variables − fijos = the Resultado operacional."
              },
              "months": {
                "type": "integer"
              }
            },
            "required": [
              "ingresos_clp",
              "variables_clp",
              "sin_clasificar_clp",
              "fijos_clp",
              "contribucion_clp",
              "contribucion_pct",
              "equilibrio_clp",
              "equilibrio_sin_calculo",
              "brecha_clp",
              "cobertura_pct",
              "resultado_clp",
              "months"
            ],
            "description": "Monthly averages over the range; equilibrio = average fijos ÷ the range’s margin %."
          },
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "account_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "path": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "costo",
                    "gasto"
                  ]
                },
                "behavior": {
                  "type": "string",
                  "enum": [
                    "fijo",
                    "variable"
                  ]
                },
                "behavior_inherited": {
                  "type": "boolean"
                },
                "usual_monthly_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Fijo accounts: median of the last 3 non-zero closed months within 12 months before the current one. Null for variable accounts or when never seen."
                },
                "recurring": {
                  "type": "boolean",
                  "description": "The recurring (non-zero in 3 of the 6 months up to its last booking) and stable (last amount within 25 % of the usual) test holds."
                },
                "missing_since": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "`YYYY-MM`: the account is missing in the last closed month; the first month of that run of zeros. Null otherwise."
                },
                "values": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  },
                  "description": "One value per entry of `months`, same order. Whole CLP."
                },
                "total_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "account_id",
                "name",
                "path",
                "kind",
                "behavior",
                "behavior_inherited",
                "usual_monthly_clp",
                "recurring",
                "missing_since",
                "values",
                "total_clp"
              ]
            }
          },
          "dentist_share": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "account_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              },
              "source": {
                "type": "string",
                "enum": [
                  "settings",
                  "detected",
                  "none"
                ]
              },
              "values": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "One value per entry of `months`, same order. Whole CLP."
              },
              "total_clp": {
                "type": "integer"
              },
              "pct": {
                "type": "array",
                "items": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "pct_total": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal."
              },
              "reference": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "share_bps": {
                    "type": "integer"
                  },
                  "label": {
                    "type": "string"
                  },
                  "values": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "integer"
                    },
                    "description": "One value per entry of `months`, same order. Whole CLP."
                  },
                  "total_clp": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  }
                },
                "required": [
                  "enabled",
                  "share_bps",
                  "label",
                  "values",
                  "total_clp"
                ]
              }
            },
            "required": [
              "account_ids",
              "source",
              "values",
              "total_clp",
              "pct",
              "pct_total",
              "reference"
            ],
            "description": "The treating dentist’s share (a variable cost, already inside `variables_clp`) and the optional reference line («Un tercio de la venta» by default). Clinics only."
          },
          "hora_sillon": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "base": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "months": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "The months behind the headline cost per hour, oldest first: the same last 3 complete months as `equilibrio`."
                  },
                  "fijos_clp": {
                    "type": "integer",
                    "description": "Mean fijos of those months."
                  },
                  "fijos_faltantes_clp": {
                    "type": "integer",
                    "description": "Mean «fijos que aún no aparecen» of those months (also inside `equilibrio`)."
                  },
                  "available_hours": {
                    "type": "number",
                    "description": "Mean available chair-hours of those months."
                  },
                  "costo_hora_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "(fijos + fijos faltantes) ÷ available hours: the headline «Costo hora sillón». Null with no hours."
                  },
                  "occupancy_month": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The last complete month of the range: occupancy and attended hours are read from it. Null when the range has none."
                  }
                },
                "required": [
                  "months",
                  "fijos_clp",
                  "fijos_faltantes_clp",
                  "available_hours",
                  "costo_hora_clp",
                  "occupancy_month"
                ],
                "description": "Base of the headline cost per hour (D12). Null when there is no complete month."
              },
              "chairs": {
                "type": "integer",
                "description": "Total chairs counted (same as `chairs_source.count`)."
              },
              "chairs_source": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "resources",
                      "override",
                      "none"
                    ],
                    "description": "`resources`: the clinic’s active boxes/sillones (native or mirrored from its previous system); `override`: the owner’s count; `none`: no chairs known (ask «¿Cuántos sillones tienes?»)."
                  },
                  "source_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "User-facing origin: «de tu agenda», «de Dentalink», «de Medilink», «elegido por ti»; null when `source` is `none`."
                  }
                },
                "required": [
                  "count",
                  "source",
                  "source_label"
                ],
                "description": "Where the chair count comes from."
              },
              "lunch_break_minutes": {
                "type": "integer",
                "description": "Weekday (Mon–Fri) lunch break already subtracted from `available_hours` on days open 6 h or more."
              },
              "available_hours": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "Hours per entry of `months`, one decimal."
              },
              "attended_hours": {
                "type": "array",
                "items": {
                  "type": "number"
                },
                "description": "Hours per entry of `months`, one decimal."
              },
              "available_hours_total": {
                "type": "number"
              },
              "attended_hours_total": {
                "type": "number"
              },
              "production_clp": {
                "type": "array",
                "items": {
                  "type": "integer"
                },
                "description": "One value per entry of `months`, same order. Whole CLP."
              },
              "production_total_clp": {
                "type": "integer"
              },
              "production_source": {
                "type": "string",
                "enum": [
                  "clinic_mirror",
                  "vitrina"
                ]
              },
              "occupancy_pct": {
                "type": "array",
                "items": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "occupancy_pct_total": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal."
              },
              "costo_hora_disponible_clp": {
                "type": "array",
                "items": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              },
              "costo_hora_disponible_total_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "costo_hora_atendida_clp": {
                "type": "array",
                "items": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              },
              "costo_hora_atendida_total_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "produccion_hora_atendida_clp": {
                "type": "array",
                "items": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              },
              "produccion_hora_atendida_total_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "branches": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "branch_id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "chairs": {
                      "type": "integer"
                    },
                    "available_hours": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      },
                      "description": "Hours per entry of `months`, one decimal."
                    },
                    "attended_hours": {
                      "type": "array",
                      "items": {
                        "type": "number"
                      },
                      "description": "Hours per entry of `months`, one decimal."
                    },
                    "available_hours_total": {
                      "type": "number"
                    },
                    "attended_hours_total": {
                      "type": "number"
                    },
                    "occupancy_pct": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Percent, one decimal."
                    }
                  },
                  "required": [
                    "branch_id",
                    "name",
                    "chairs",
                    "available_hours",
                    "attended_hours",
                    "available_hours_total",
                    "attended_hours_total",
                    "occupancy_pct"
                  ]
                }
              },
              "missing": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "chairs",
                    "opening_hours",
                    "appointments"
                  ]
                }
              }
            },
            "required": [
              "base",
              "chairs",
              "chairs_source",
              "lunch_break_minutes",
              "available_hours",
              "attended_hours",
              "available_hours_total",
              "attended_hours_total",
              "production_clp",
              "production_total_clp",
              "production_source",
              "occupancy_pct",
              "occupancy_pct_total",
              "costo_hora_disponible_clp",
              "costo_hora_disponible_total_clp",
              "costo_hora_atendida_clp",
              "costo_hora_atendida_total_clp",
              "produccion_hora_atendida_clp",
              "produccion_hora_atendida_total_clp",
              "branches",
              "missing"
            ],
            "description": "Costo hora sillón (clinics only): fijos ÷ available chair-hours (chairs × opening hours), all costs ÷ attended hours, occupancy and production per attended hour, per branch and total."
          }
        },
        "required": [
          "from",
          "to",
          "current_month",
          "currency",
          "is_clinic",
          "reference_month",
          "months",
          "equilibrio",
          "equilibrio_produccion",
          "period",
          "accounts",
          "dentist_share",
          "hora_sillon"
        ]
      },
      "FinanzasCostosSettings": {
        "type": "object",
        "properties": {
          "reference_share_enabled": {
            "type": "boolean",
            "description": "Show the reference line next to the dentist share."
          },
          "reference_share_bps": {
            "type": "integer",
            "description": "Its share of income in basis points (3333 = a third)."
          },
          "professional_account_ids": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The accounts that are the treating dentist’s share (with everything under them); null = detected from the plan de cuentas."
          },
          "effective_professional_account_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "chairs_override": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Chairs the owner says the clinic has (1–200); wins over the resources. Null = count the clinic’s resources."
          },
          "lunch_break_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Weekday lunch break in minutes taken off the available chair-hours; null = the default, 60."
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "reference_share_enabled",
          "reference_share_bps",
          "professional_account_ids",
          "effective_professional_account_ids",
          "chairs_override",
          "lunch_break_minutes",
          "updated_by",
          "updated_at"
        ]
      },
      "FinanzasResumenCash": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "today_clp": {
            "type": "integer",
            "description": "«Caja hoy»: today’s balance of the operating accounts (savings apart) = the Flujo de caja opening."
          },
          "balance_as_of": {
            "type": [
              "string",
              "null"
            ]
          },
          "accounts_count": {
            "type": "integer"
          },
          "unknown_count": {
            "type": "integer",
            "description": "Operating accounts with no known balance (not in the total)."
          },
          "savings_clp": {
            "type": "integer"
          },
          "month_end": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string"
              },
              "balance_clp": {
                "type": "integer",
                "description": "The confirmed balance: bank balances plus known items only (no typical income, no «Vencidas sin pago visto»)."
              },
              "expected_balance_clp": {
                "type": "integer",
                "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income of the last 90 days (the dashed line). Never a headline."
              },
              "band": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "low_clp": {
                    "type": "integer"
                  },
                  "high_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "low_clp",
                  "high_clp"
                ],
                "description": "Retired with the uncertainty band (#6993): always null. Read `expected_balance_clp`."
              }
            },
            "required": [
              "date",
              "balance_clp",
              "expected_balance_clp",
              "band"
            ],
            "description": "Projected balance at the end of the month."
          },
          "in_30_days": {
            "type": "object",
            "properties": {
              "date": {
                "type": "string"
              },
              "balance_clp": {
                "type": "integer",
                "description": "The confirmed balance: bank balances plus known items only (no typical income, no «Vencidas sin pago visto»)."
              },
              "expected_balance_clp": {
                "type": "integer",
                "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income of the last 90 days (the dashed line). Never a headline."
              },
              "band": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "low_clp": {
                    "type": "integer"
                  },
                  "high_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "low_clp",
                  "high_clp"
                ],
                "description": "Retired with the uncertainty band (#6993): always null. Read `expected_balance_clp`."
              }
            },
            "required": [
              "date",
              "balance_clp",
              "expected_balance_clp",
              "band"
            ],
            "description": "Projected balance 30 days from today."
          },
          "lowest": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCashFlowLowest"
              },
              {
                "description": "Lowest balance over today … today + 30, today included: never above `today_clp`; `is_today` when today is the lowest."
              }
            ]
          },
          "runs_out": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "date": {
                "type": "string",
                "description": "The first projected day under zero."
              },
              "covered_until": {
                "type": "string",
                "description": "«Alcanza hasta el …»: the day before."
              },
              "balance_clp": {
                "type": "integer"
              },
              "cause": {
                "$ref": "#/components/schemas/FinanzasCashFlowItem"
              }
            },
            "required": [
              "date",
              "covered_until",
              "balance_clp",
              "cause"
            ],
            "description": "Null when the confirmed balance stays ≥ 0 for 30 days. `cause` is the payment that takes it below zero (that day’s biggest payment when it already starts below zero)."
          },
          "overdue": {
            "type": "object",
            "properties": {
              "payables_clp": {
                "type": "integer"
              },
              "payables_count": {
                "type": "integer"
              },
              "stale_payables_clp": {
                "type": "integer"
              },
              "stale_payables_count": {
                "type": "integer"
              },
              "receivables_clp": {
                "type": "integer"
              },
              "receivables_count": {
                "type": "integer"
              }
            },
            "required": [
              "payables_clp",
              "payables_count",
              "stale_payables_clp",
              "stale_payables_count",
              "receivables_clp",
              "receivables_count"
            ]
          },
          "overdue_no_payment_seen": {
            "$ref": "#/components/schemas/FinanzasCashFlowOverdueNoPaymentSeen"
          },
          "inherited_overdue": {
            "$ref": "#/components/schemas/FinanzasInheritedOverdue"
          },
          "points": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string"
                },
                "balance_clp": {
                  "type": "integer",
                  "description": "The confirmed balance: bank balances plus known items only (no typical income, no «Vencidas sin pago visto»)."
                },
                "expected_balance_clp": {
                  "type": "integer",
                  "description": "«Con cobros esperados»: the confirmed balance plus the typical weekday income of the last 90 days (the dashed line). Never a headline."
                },
                "band": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "low_clp": {
                      "type": "integer"
                    },
                    "high_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "low_clp",
                    "high_clp"
                  ],
                  "description": "Retired with the uncertainty band (#6993): always null. Read `expected_balance_clp`."
                }
              },
              "required": [
                "date",
                "balance_clp",
                "expected_balance_clp",
                "band"
              ]
            },
            "description": "The runway: one point per day, today … today + 30."
          }
        },
        "required": [
          "today_clp",
          "balance_as_of",
          "accounts_count",
          "unknown_count",
          "savings_clp",
          "month_end",
          "in_30_days",
          "lowest",
          "runs_out",
          "overdue",
          "overdue_no_payment_seen",
          "inherited_overdue",
          "points"
        ]
      },
      "FinanzasResumenResult": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "month": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "proforma": {
            "type": "boolean"
          },
          "ingresos_clp": {
            "type": "integer",
            "description": "Net, as the Resultado."
          },
          "gastos_clp": {
            "type": "integer",
            "description": "«¿Cuánto gasto?»: variables + fijos."
          },
          "variables_clp": {
            "type": "integer"
          },
          "fijos_clp": {
            "type": "integer"
          },
          "sin_clasificar_clp": {
            "type": "integer",
            "description": "Part of `variables_clp`: «Egresos sin clasificar», shown apart."
          },
          "resultado_clp": {
            "type": "integer",
            "description": "The month’s Resultado operacional."
          },
          "produccion_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Clinics with production data: the month’s producción (performed work in the clinic system). Null for the others."
          },
          "basis": {
            "type": "string",
            "enum": [
              "produccion",
              "ingresos"
            ],
            "description": "What `basis_clp`, `cobertura_pct` and `brecha_clp` measure against the break-even: `produccion` mid-month for a clinic with production data (booked income reads near zero until the month’s summary boleta arrives), `ingresos` otherwise and once booked income covers 80 % of the month’s producción."
          },
          "basis_clp": {
            "type": "integer",
            "description": "The amount on `basis`."
          },
          "equilibrio": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "month": {
                "type": "string",
                "description": "The newest month used."
              },
              "label": {
                "type": "string"
              },
              "equilibrio_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The bar: the average break-even of the last 3 complete months, «Mes incompleto» skipped. Never null today (the whole `equilibrio` is null instead); kept nullable for compatibility."
              },
              "sin_calculo": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "sin_ingresos",
                  "margen_no_positivo"
                ],
                "description": "Always null: only months with a break-even are averaged."
              },
              "from": {
                "type": "string",
                "description": "First month of the average, `YYYY-MM`."
              },
              "to": {
                "type": "string",
                "description": "Last month of the average, `YYYY-MM`."
              },
              "months": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "equilibrio_clp": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "month",
                    "label",
                    "equilibrio_clp"
                  ]
                },
                "description": "The trailing complete months used, oldest first (at most 3)."
              },
              "produccion": {
                "$ref": "#/components/schemas/FinanzasEquilibrioProduccion"
              },
              "skipped_incompleto": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "month": {
                    "type": "string"
                  },
                  "label": {
                    "type": "string"
                  },
                  "message": {
                    "type": "string",
                    "description": "Spanish, ready for the tooltip."
                  }
                },
                "required": [
                  "month",
                  "label",
                  "message"
                ],
                "description": "The newest «Mes incompleto» the bar skipped, so the card can flag it like Resultado and Costos do. Null when no month was skipped."
              }
            },
            "required": [
              "month",
              "label",
              "equilibrio_clp",
              "sin_calculo",
              "from",
              "to",
              "months",
              "produccion",
              "skipped_incompleto"
            ],
            "description": "The break-even bar the month is judged against. Null when no complete month has a break-even yet."
          },
          "target_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The break-even on the same terms as `basis`: `equilibrio.produccion.equilibrio_clp` when `basis` is `produccion` and a ratio exists, else `equilibrio.equilibrio_clp`. Null without a break-even."
          },
          "cobertura_pct": {
            "type": [
              "number",
              "null"
            ],
            "description": "`basis_clp` ÷ `target_clp`, one decimal."
          },
          "brecha_clp": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`basis_clp` − `target_clp` (negative: still short of it)."
          }
        },
        "required": [
          "month",
          "label",
          "proforma",
          "ingresos_clp",
          "gastos_clp",
          "variables_clp",
          "fijos_clp",
          "sin_clasificar_clp",
          "resultado_clp",
          "produccion_clp",
          "basis",
          "basis_clp",
          "equilibrio",
          "target_clp",
          "cobertura_pct",
          "brecha_clp"
        ]
      },
      "FinanzasResumen": {
        "type": "object",
        "properties": {
          "as_of": {
            "type": "string",
            "description": "Today (America/Santiago)."
          },
          "month": {
            "type": "string"
          },
          "month_label": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP"
            ]
          },
          "reports_visible": {
            "type": "boolean",
            "description": "False for a caller without `finanzas_reports:read` (the «Finanzas» staff permission): `cash` and `result` are null."
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The newest successful bank / Mercado Pago sync."
          },
          "problem": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "cash_runs_out"
                    ]
                  },
                  "date": {
                    "type": "string"
                  },
                  "balance_clp": {
                    "type": "integer"
                  }
                },
                "required": [
                  "kind",
                  "date",
                  "balance_clp"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "connection"
                    ]
                  },
                  "connection_id": {
                    "type": "string"
                  },
                  "institution_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "ok",
                      "first_sync",
                      "pending",
                      "stale",
                      "error",
                      "login_required",
                      "disconnected"
                    ]
                  }
                },
                "required": [
                  "kind",
                  "connection_id",
                  "institution_name",
                  "status"
                ]
              },
              {
                "type": "null"
              }
            ],
            "description": "The one problem shown in red: cash running out within 30 days first, else a bank connection that needs a person."
          },
          "cash": {
            "$ref": "#/components/schemas/FinanzasResumenCash"
          },
          "result": {
            "$ref": "#/components/schemas/FinanzasResumenResult"
          },
          "inbox": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinanzasCaseCount"
              },
              {
                "type": "object",
                "properties": {
                  "close": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "month": {
                        "type": "string"
                      },
                      "label": {
                        "type": "string"
                      },
                      "state": {
                        "type": "string",
                        "enum": [
                          "open",
                          "ready_to_close",
                          "closed",
                          "closed_imported"
                        ]
                      },
                      "open_cases": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "month",
                      "label",
                      "state",
                      "open_cases"
                    ],
                    "description": "The previous month, for «Cerrar {mes}» when nothing is open. Null (no prompt) when that month is before the month the add-on became active and holds no bank movements or documents."
                  }
                },
                "required": [
                  "close"
                ]
              }
            ]
          },
          "automatic": {
            "type": "object",
            "properties": {
              "month": {
                "type": "string"
              },
              "count": {
                "type": "integer"
              },
              "total_clp": {
                "type": "integer"
              },
              "unreviewed_count": {
                "type": "integer"
              },
              "by_actor": {
                "type": "object",
                "properties": {
                  "rule": {
                    "type": "integer"
                  },
                  "match": {
                    "type": "integer"
                  },
                  "ai": {
                    "type": "integer"
                  }
                },
                "required": [
                  "rule",
                  "match",
                  "ai"
                ]
              },
              "recent": {
                "type": "array",
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/FinanzasAutomaticAction"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "group_name": {
                          "type": "string"
                        },
                        "group_key": {
                          "type": "string",
                          "description": "The Hecho por Vitrina group of the action: `own_transfers`, `cash_deposits`, `clinic_payments`, `contraparte:<id>` or, for the unassigned ones, `none:<accountId>`."
                        },
                        "group_count": {
                          "type": "integer",
                          "description": "Actions in the whole group this month."
                        },
                        "group_total_clp": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "group_name",
                        "group_key",
                        "group_count",
                        "group_total_clp"
                      ]
                    }
                  ]
                },
                "description": "The newest 3 groups of automatic actions of the month, one row each: the group's newest action plus the group's count and total. Undo an action through POST /finanzas/automatic-actions/{kind}/{id}/undo."
              }
            },
            "required": [
              "month",
              "count",
              "total_clp",
              "unreviewed_count",
              "by_actor",
              "recent"
            ]
          },
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "institution_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "number_masked": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "type": "string"
                },
                "currency": {
                  "type": "string",
                  "enum": [
                    "CLP",
                    "USD"
                  ]
                },
                "balance_clp": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "balance_as_of": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "data_through": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "stale": {
                  "type": "boolean",
                  "description": "true = no connection feeds the account and its newest movement is older than 37 days (D9): show «Datos hasta el …» as a warning with the cartola upload."
                },
                "in_cash": {
                  "type": "boolean",
                  "description": "False for savings accounts (not in «Caja hoy»)."
                }
              },
              "required": [
                "id",
                "name",
                "institution_name",
                "number_masked",
                "type",
                "currency",
                "balance_clp",
                "balance_as_of",
                "data_through",
                "stale",
                "in_cash"
              ]
            }
          }
        },
        "required": [
          "as_of",
          "month",
          "month_label",
          "currency",
          "reports_visible",
          "synced_at",
          "problem",
          "cash",
          "result",
          "inbox",
          "automatic",
          "accounts"
        ]
      },
      "FinanzasPuente": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "current_month": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "enum": [
              "CLP"
            ]
          },
          "is_clinic": {
            "type": "boolean"
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "clinic_mirror",
              "vitrina"
            ],
            "description": "Where producción and cobrado come from: the clinic system mirror (Dentalink/Medilink) or Vitrina’s own registers. Never both."
          },
          "boletas_source": {
            "type": "string",
            "enum": [
              "finanzas",
              "clinic_register",
              "none"
            ]
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "proforma": {
                  "type": "boolean"
                },
                "produccion_clp": {
                  "type": "integer",
                  "description": "Treatments performed (the clinic system’s value)."
                },
                "cobrado_clp": {
                  "type": "integer",
                  "description": "Patient payments received."
                },
                "boleteado_clp": {
                  "type": "integer",
                  "description": "Boletas issued (DTE 39/41, at totals), minus notas de crédito on boletas."
                },
                "por_cobrar_clp": {
                  "type": "integer",
                  "description": "Produced and not paid in the period, per patient."
                },
                "anticipos_clp": {
                  "type": "integer",
                  "description": "Paid and not produced in the period, per patient."
                },
                "cobrado_sin_boleta_clp": {
                  "type": "integer"
                },
                "boletas_sin_pago_clp": {
                  "type": "integer"
                },
                "ingresos_resultado_clp": {
                  "type": "integer",
                  "description": "The Resultado’s «Ingresos» (issued documents, net) for the same months."
                },
                "pagos_sin_boleta": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Payments (or payment parts) without a boleta: the count behind `cobrado_sin_boleta_clp`."
                },
                "pendiente_resumen_clp": {
                  "type": "integer",
                  "description": "Part of `cobrado_sin_boleta_clp` waiting for next month’s summary boleta, which is not issued yet (reason `resumen_pendiente`). Included in `cobrado_sin_boleta_clp`."
                },
                "pagos_pendiente_resumen": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Count behind `pendiente_resumen_clp`; included in `pagos_sin_boleta`."
                },
                "boleteado_otro_mes_clp": {
                  "type": "integer",
                  "description": "Signed: payments of the previous month covered by THIS month’s summary boleta (+), minus payments of this month covered by the NEXT month’s summary (−). cobrado − cobrado_sin_boleta + boletas_sin_pago + boleteado_otro_mes = boleteado, apart from notas de crédito that name no boleta (they lower `boleteado_clp` but not a boleta’s capacity)."
                }
              },
              "required": [
                "month",
                "label",
                "proforma",
                "produccion_clp",
                "cobrado_clp",
                "boleteado_clp",
                "por_cobrar_clp",
                "anticipos_clp",
                "cobrado_sin_boleta_clp",
                "boletas_sin_pago_clp",
                "ingresos_resultado_clp",
                "pagos_sin_boleta",
                "pendiente_resumen_clp",
                "pagos_pendiente_resumen",
                "boleteado_otro_mes_clp"
              ]
            }
          },
          "total": {
            "type": "object",
            "properties": {
              "produccion_clp": {
                "type": "integer",
                "description": "Treatments performed (the clinic system’s value)."
              },
              "cobrado_clp": {
                "type": "integer",
                "description": "Patient payments received."
              },
              "boleteado_clp": {
                "type": "integer",
                "description": "Boletas issued (DTE 39/41, at totals), minus notas de crédito on boletas."
              },
              "por_cobrar_clp": {
                "type": "integer",
                "description": "Produced and not paid in the period, per patient."
              },
              "anticipos_clp": {
                "type": "integer",
                "description": "Paid and not produced in the period, per patient."
              },
              "cobrado_sin_boleta_clp": {
                "type": "integer"
              },
              "boletas_sin_pago_clp": {
                "type": "integer"
              },
              "ingresos_resultado_clp": {
                "type": "integer",
                "description": "The Resultado’s «Ingresos» (issued documents, net) for the same months."
              },
              "pagos_sin_boleta": {
                "type": "integer",
                "minimum": 0,
                "description": "Payments (or payment parts) without a boleta: the count behind `cobrado_sin_boleta_clp`."
              },
              "pendiente_resumen_clp": {
                "type": "integer",
                "description": "Part of `cobrado_sin_boleta_clp` waiting for next month’s summary boleta, which is not issued yet (reason `resumen_pendiente`). Included in `cobrado_sin_boleta_clp`."
              },
              "pagos_pendiente_resumen": {
                "type": "integer",
                "minimum": 0,
                "description": "Count behind `pendiente_resumen_clp`; included in `pagos_sin_boleta`."
              },
              "boleteado_otro_mes_clp": {
                "type": "integer",
                "description": "Signed: payments of the previous month covered by THIS month’s summary boleta (+), minus payments of this month covered by the NEXT month’s summary (−). cobrado − cobrado_sin_boleta + boletas_sin_pago + boleteado_otro_mes = boleteado, apart from notas de crédito that name no boleta (they lower `boleteado_clp` but not a boleta’s capacity)."
              }
            },
            "required": [
              "produccion_clp",
              "cobrado_clp",
              "boleteado_clp",
              "por_cobrar_clp",
              "anticipos_clp",
              "cobrado_sin_boleta_clp",
              "boletas_sin_pago_clp",
              "ingresos_resultado_clp",
              "pagos_sin_boleta",
              "pendiente_resumen_clp",
              "pagos_pendiente_resumen",
              "boleteado_otro_mes_clp"
            ],
            "description": "The range, netted per patient."
          },
          "payments_without_boleta": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "total_clp": {
                "type": "integer"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "payment_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "clinic_mirror",
                        "vitrina"
                      ]
                    },
                    "day": {
                      "type": "string"
                    },
                    "amount_clp": {
                      "type": "integer"
                    },
                    "without_boleta_clp": {
                      "type": "integer"
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "sin_boleta",
                        "boleta_no_encontrada",
                        "resumen_pendiente"
                      ]
                    },
                    "folios": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "patient": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string",
                          "description": "Full name; «Paciente A. P.» for a caller without the patient roster."
                        },
                        "rut": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "rut"
                      ]
                    }
                  },
                  "required": [
                    "payment_id",
                    "source",
                    "day",
                    "amount_clp",
                    "without_boleta_clp",
                    "reason",
                    "folios",
                    "patient"
                  ]
                }
              },
              "by_month": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "total_clp": {
                      "type": "integer"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "payment_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "source": {
                            "type": "string",
                            "enum": [
                              "clinic_mirror",
                              "vitrina"
                            ]
                          },
                          "day": {
                            "type": "string"
                          },
                          "amount_clp": {
                            "type": "integer"
                          },
                          "without_boleta_clp": {
                            "type": "integer"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "sin_boleta",
                              "boleta_no_encontrada",
                              "resumen_pendiente"
                            ]
                          },
                          "folios": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "patient": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string",
                                "description": "Full name; «Paciente A. P.» for a caller without the patient roster."
                              },
                              "rut": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "rut"
                            ]
                          }
                        },
                        "required": [
                          "payment_id",
                          "source",
                          "day",
                          "amount_clp",
                          "without_boleta_clp",
                          "reason",
                          "folios",
                          "patient"
                        ]
                      }
                    }
                  },
                  "required": [
                    "month",
                    "count",
                    "total_clp",
                    "items"
                  ]
                },
                "description": "Months with a gap, ascending, each with its `per_month` largest items (patients redacted like `items`)."
              }
            },
            "required": [
              "count",
              "total_clp",
              "items",
              "by_month"
            ]
          },
          "boletas_without_payment": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "total_clp": {
                "type": "integer"
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "document_id": {
                      "type": "string"
                    },
                    "dte_type": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "folio": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "day": {
                      "type": "string"
                    },
                    "total_clp": {
                      "type": "integer"
                    },
                    "without_payment_clp": {
                      "type": "integer"
                    },
                    "receiver": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "rut": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "patient_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "name",
                        "rut",
                        "patient_id"
                      ]
                    },
                    "monthly_summary": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "document_id",
                    "dte_type",
                    "folio",
                    "day",
                    "total_clp",
                    "without_payment_clp",
                    "receiver",
                    "monthly_summary"
                  ]
                }
              }
            },
            "required": [
              "count",
              "total_clp",
              "items"
            ]
          },
          "monthly_boletas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "document_id": {
                  "type": "string"
                },
                "dte_type": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "folio": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "day": {
                  "type": "string"
                },
                "month": {
                  "type": "string"
                },
                "total_clp": {
                  "type": "integer"
                },
                "covered_clp": {
                  "type": "integer"
                },
                "payments": {
                  "type": "integer"
                }
              },
              "required": [
                "document_id",
                "dte_type",
                "folio",
                "day",
                "month",
                "total_clp",
                "covered_clp",
                "payments"
              ]
            }
          }
        },
        "required": [
          "from",
          "to",
          "current_month",
          "currency",
          "is_clinic",
          "source",
          "boletas_source",
          "months",
          "total",
          "payments_without_boleta",
          "boletas_without_payment",
          "monthly_boletas"
        ]
      },
      "FinanzasAdsConnections": {
        "type": "object",
        "properties": {
          "profile": {
            "type": "boolean",
            "description": "The workspace has its ad-data link (provisioned when it connects Meta Ads)."
          },
          "last_attempt_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_sync_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Spanish copy when the last read failed, else null."
          },
          "platforms": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "platform": {
                  "type": "string",
                  "enum": [
                    "meta_ads",
                    "google_ads"
                  ]
                },
                "label": {
                  "type": "string",
                  "example": "Meta Ads"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "connected",
                    "expired",
                    "none"
                  ],
                  "description": "connected = at least one healthy account; expired = accounts, none healthy; none = no account."
                },
                "accounts": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "platform": {
                        "type": "string",
                        "enum": [
                          "meta_ads",
                          "google_ads",
                          "tiktok_ads"
                        ]
                      },
                      "ad_account_id": {
                        "type": "string",
                        "description": "The platform's own id: Meta `act_…`, Google the 10-digit customer id.",
                        "example": "act_969938652650502"
                      },
                      "account_label": {
                        "type": "string",
                        "description": "Google: `123-456-7890`; Meta: the `act_…` id."
                      },
                      "account_name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "currency": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "timezone": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "connection_status": {
                        "type": "string",
                        "description": "connected | syncing | error | disconnected | reconnect_required | revoked"
                      },
                      "healthy": {
                        "type": "boolean",
                        "description": "connected or syncing."
                      },
                      "last_synced_at": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "charges_supported": {
                        "type": "boolean",
                        "description": "True only for Meta."
                      },
                      "charges_synced_at": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "description": "When Meta's activity log was last read in full; null = never."
                      },
                      "charges_synced": {
                        "type": "integer"
                      },
                      "charges_linked": {
                        "type": "integer"
                      },
                      "charges_unreconciled": {
                        "type": "integer",
                        "description": "Billing charges older than a week that no card line has been matched to («por conciliar»)."
                      },
                      "spend_by_month": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "month": {
                              "type": "string",
                              "example": "2026-10"
                            },
                            "spend_minor": {
                              "type": "integer"
                            },
                            "currency": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "month",
                            "spend_minor",
                            "currency"
                          ]
                        },
                        "description": "Spend per month over the last read (92 days), smallest unit of `currency`."
                      }
                    },
                    "required": [
                      "platform",
                      "ad_account_id",
                      "account_label",
                      "account_name",
                      "currency",
                      "timezone",
                      "connection_status",
                      "healthy",
                      "last_synced_at",
                      "charges_supported",
                      "charges_synced_at",
                      "charges_synced",
                      "charges_linked",
                      "charges_unreconciled",
                      "spend_by_month"
                    ]
                  }
                },
                "without_connection": {
                  "type": "string"
                },
                "connect_path": {
                  "type": "string",
                  "description": "Where to connect: Integraciones › Meta Ads (no connect flow here)."
                }
              },
              "required": [
                "platform",
                "label",
                "status",
                "accounts",
                "without_connection",
                "connect_path"
              ]
            }
          }
        },
        "required": [
          "profile",
          "last_attempt_at",
          "last_sync_at",
          "last_error",
          "platforms"
        ]
      },
      "FinanzasAdsCharge": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "platform": {
            "type": "string",
            "enum": [
              "meta_ads",
              "google_ads",
              "tiktok_ads"
            ]
          },
          "ad_account_id": {
            "type": "string"
          },
          "event_type": {
            "type": "string"
          },
          "event_label": {
            "type": "string",
            "description": "cargo | reembolso | contracargo | rechazo | fondos"
          },
          "event_time": {
            "type": "string"
          },
          "local_date": {
            "type": "string"
          },
          "meta_transaction_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta's «Transaction ID» (Billing & payments), unique per charge."
          },
          "amount_minor": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Smallest unit of `currency` (CLP pesos, USD cents). Null when Meta stated none."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "link_state": {
            "type": "string",
            "enum": [
              "linked",
              "unlinked"
            ]
          },
          "document_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "movement_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "linked_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "platform",
          "ad_account_id",
          "event_type",
          "event_label",
          "event_time",
          "local_date",
          "meta_transaction_id",
          "amount_minor",
          "currency",
          "link_state",
          "document_id",
          "movement_id",
          "linked_at"
        ]
      },
      "FinanzasAdsChargesList": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FinanzasAdsCharge"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "linked": {
                "type": "integer"
              },
              "unlinked": {
                "type": "integer"
              }
            },
            "required": [
              "count",
              "linked",
              "unlinked"
            ]
          }
        },
        "required": [
          "from",
          "to",
          "items",
          "totals"
        ]
      },
      "FinanzasAdsSyncResult": {
        "type": "object",
        "properties": {
          "skipped": {
            "type": "boolean"
          },
          "read": {
            "type": "string",
            "enum": [
              "ok",
              "no_profile",
              "error",
              "skipped"
            ]
          },
          "accounts": {
            "type": "integer"
          },
          "charges_new": {
            "type": "integer"
          },
          "recognize": {
            "type": "object",
            "properties": {
              "recognized": {
                "type": "integer"
              },
              "created": {
                "type": "integer"
              },
              "receipt_first": {
                "type": "integer"
              }
            },
            "required": [
              "recognized",
              "created",
              "receipt_first"
            ]
          },
          "match": {
            "type": "object",
            "properties": {
              "open": {
                "type": "integer"
              },
              "linked": {
                "type": "integer"
              },
              "unconfirmed": {
                "type": "integer"
              },
              "no_connection": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              }
            },
            "required": [
              "open",
              "linked",
              "unconfirmed",
              "no_connection",
              "pending"
            ]
          }
        },
        "required": [
          "skipped",
          "read",
          "accounts",
          "charges_new",
          "recognize",
          "match"
        ]
      },
      "ClinicImportedBalanceBudget": {
        "type": "object",
        "properties": {
          "budget_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string",
            "example": "E-412"
          },
          "status": {
            "type": "string",
            "example": "accepted"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "18233"
          },
          "plan_date": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-10-28"
          },
          "cutover_balance_clp": {
            "type": "integer"
          },
          "collected_clp": {
            "type": "integer"
          },
          "balance_clp": {
            "type": "integer"
          }
        },
        "required": [
          "budget_id",
          "display_id",
          "status",
          "external_id",
          "plan_date",
          "cutover_balance_clp",
          "collected_clp",
          "balance_clp"
        ]
      },
      "ClinicImportedBalanceRow": {
        "type": "object",
        "properties": {
          "patient": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "nombre": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "apellidos": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "numero_ficha": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "nombre",
              "apellidos",
              "rut",
              "numero_ficha"
            ]
          },
          "balance_clp": {
            "type": "integer"
          },
          "cutover_balance_clp": {
            "type": "integer"
          },
          "collected_clp": {
            "type": "integer"
          },
          "last_vendor_payment_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-10-28"
          },
          "last_collected_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-10-28"
          },
          "last_payment_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "example": "2026-10-28"
          },
          "budgets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicImportedBalanceBudget"
            }
          }
        },
        "required": [
          "patient",
          "balance_clp",
          "cutover_balance_clp",
          "collected_clp",
          "last_vendor_payment_on",
          "last_collected_on",
          "last_payment_on",
          "budgets"
        ]
      },
      "ClinicImportedBalanceList": {
        "type": "object",
        "properties": {
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicImportedBalanceRow"
            }
          },
          "total": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "summary": {
            "type": "object",
            "properties": {
              "patients": {
                "type": "integer"
              },
              "balance_clp": {
                "type": "integer"
              },
              "cutover_balance_clp": {
                "type": "integer"
              },
              "collected_clp": {
                "type": "integer"
              }
            },
            "required": [
              "patients",
              "balance_clp",
              "cutover_balance_clp",
              "collected_clp"
            ]
          }
        },
        "required": [
          "rows",
          "total",
          "offset",
          "limit",
          "summary"
        ]
      },
      "ClinicImportedBalanceDetail": {
        "type": "object",
        "properties": {
          "budget_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "clinic_patient_id": {
            "type": "string",
            "format": "uuid"
          },
          "cutover_balance_clp": {
            "type": "integer"
          },
          "balance_clp": {
            "type": "integer"
          },
          "payer": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "contact_id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "contact_id",
              "name"
            ]
          },
          "will_bond_payer": {
            "type": "boolean"
          },
          "credits": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "payment_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "paid_on": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                  "example": "2026-10-28"
                },
                "instrument": {
                  "type": "string"
                },
                "amount_clp": {
                  "type": "integer"
                },
                "unallocated_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "payment_id",
                "paid_on",
                "instrument",
                "amount_clp",
                "unallocated_clp"
              ]
            }
          }
        },
        "required": [
          "budget_id",
          "display_id",
          "status",
          "clinic_patient_id",
          "cutover_balance_clp",
          "balance_clp",
          "payer",
          "will_bond_payer",
          "credits"
        ]
      },
      "ClinicImportedBalanceCollectResult": {
        "type": "object",
        "properties": {
          "budget_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "clinic_patient_id": {
            "type": "string",
            "format": "uuid"
          },
          "applied_clp": {
            "type": "integer"
          },
          "balance_before_clp": {
            "type": "integer"
          },
          "balance_after_clp": {
            "type": "integer"
          },
          "obligation_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "obligation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "payer_bond_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "payment": {
            "type": "object",
            "properties": {
              "payment": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "id"
                ]
              },
              "applied": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {}
                }
              },
              "surplus_clp": {
                "type": "integer"
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "replayed": {
                "type": "boolean"
              }
            },
            "required": [
              "payment",
              "applied",
              "surplus_clp",
              "reasons",
              "replayed"
            ]
          }
        },
        "required": [
          "budget_id",
          "display_id",
          "clinic_patient_id",
          "applied_clp",
          "balance_before_clp",
          "balance_after_clp",
          "obligation_group_id",
          "obligation_id",
          "payer_bond_id",
          "payment"
        ]
      },
      "AudienceLabels": {
        "type": "object",
        "properties": {
          "tags": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "pipelines": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "stages": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "custom_attributes": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "data_type": {
                  "type": "string"
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": [
                "label",
                "data_type",
                "options"
              ]
            }
          },
          "interest_topics": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "kind": {
                  "type": "string"
                },
                "parent_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "name",
                "kind",
                "parent_name"
              ]
            }
          },
          "missing": {
            "type": "object",
            "properties": {
              "tags": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "pipeline_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "stage_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "custom_attributes": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "interest_topics": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "tags",
              "pipeline_id",
              "stage_id",
              "custom_attributes",
              "interest_topics"
            ]
          }
        },
        "required": [
          "tags",
          "pipelines",
          "stages",
          "custom_attributes",
          "interest_topics",
          "missing"
        ]
      },
      "AudienceSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "filter": {
            "type": "object",
            "properties": {
              "lifecycle_stages": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 40
                },
                "maxItems": 10
              },
              "tag_ids_any": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 25
              },
              "tag_ids_all": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 10
              },
              "tag_ids_none": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 25
              },
              "countries": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 60
                },
                "maxItems": 25
              },
              "languages": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20
                },
                "maxItems": 25
              },
              "has_channel": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "whatsapp",
                    "email"
                  ]
                },
                "maxItems": 2
              },
              "pipeline_id": {
                "type": "string",
                "format": "uuid"
              },
              "stage_id": {
                "type": "string",
                "format": "uuid"
              },
              "created_after": {
                "type": "string",
                "format": "date-time"
              },
              "created_before": {
                "type": "string",
                "format": "date-time"
              },
              "birthday_months": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 12
                },
                "maxItems": 12
              },
              "birthday_within_days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 366
              },
              "vehicle_brands": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "maxItems": 25
              },
              "vehicle_models": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "maxItems": 25
              },
              "vehicle_years": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 1900,
                  "maximum": 2100
                },
                "maxItems": 50
              },
              "custom_attributes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 100
                    },
                    "value": {
                      "type": "string",
                      "maxLength": 500
                    },
                    "values": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "maxItems": 25
                    }
                  },
                  "required": [
                    "key"
                  ]
                },
                "maxItems": 10
              },
              "interests": {
                "type": "object",
                "properties": {
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 25
                  },
                  "within_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1095
                  },
                  "min_strength": {
                    "anyOf": [
                      {
                        "type": "number",
                        "enum": [
                          1
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          2
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          3
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          4
                        ]
                      }
                    ]
                  },
                  "include_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "topic_ids"
                ],
                "additionalProperties": false
              },
              "interests_none": {
                "type": "object",
                "properties": {
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 25
                  },
                  "min_strength": {
                    "anyOf": [
                      {
                        "type": "number",
                        "enum": [
                          1
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          2
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          3
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          4
                        ]
                      }
                    ]
                  },
                  "include_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "topic_ids"
                ],
                "additionalProperties": false
              }
            },
            "additionalProperties": false
          },
          "kind": {
            "type": "string",
            "enum": [
              "dynamic",
              "static",
              "mixed",
              "all"
            ]
          },
          "include_count": {
            "type": "integer"
          },
          "exclude_count": {
            "type": "integer"
          },
          "labels": {
            "$ref": "#/components/schemas/AudienceLabels"
          },
          "usage": {
            "type": "object",
            "properties": {
              "campaigns_count": {
                "type": "integer"
              },
              "last_used_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "active_campaigns_count": {
                "type": "integer"
              }
            },
            "required": [
              "campaigns_count",
              "last_used_at",
              "active_campaigns_count"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "filter",
          "kind",
          "include_count",
          "exclude_count",
          "labels",
          "usage",
          "created_by",
          "created_at",
          "updated_at"
        ]
      },
      "AudienceDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "filter": {
            "type": "object",
            "properties": {
              "lifecycle_stages": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 40
                },
                "maxItems": 10
              },
              "tag_ids_any": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 25
              },
              "tag_ids_all": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 10
              },
              "tag_ids_none": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                },
                "maxItems": 25
              },
              "countries": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 60
                },
                "maxItems": 25
              },
              "languages": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 20
                },
                "maxItems": 25
              },
              "has_channel": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "whatsapp",
                    "email"
                  ]
                },
                "maxItems": 2
              },
              "pipeline_id": {
                "type": "string",
                "format": "uuid"
              },
              "stage_id": {
                "type": "string",
                "format": "uuid"
              },
              "created_after": {
                "type": "string",
                "format": "date-time"
              },
              "created_before": {
                "type": "string",
                "format": "date-time"
              },
              "birthday_months": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 12
                },
                "maxItems": 12
              },
              "birthday_within_days": {
                "type": "integer",
                "minimum": 1,
                "maximum": 366
              },
              "vehicle_brands": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "maxItems": 25
              },
              "vehicle_models": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "maxItems": 25
              },
              "vehicle_years": {
                "type": "array",
                "items": {
                  "type": "integer",
                  "minimum": 1900,
                  "maximum": 2100
                },
                "maxItems": 50
              },
              "custom_attributes": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 100
                    },
                    "value": {
                      "type": "string",
                      "maxLength": 500
                    },
                    "values": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500
                      },
                      "maxItems": 25
                    }
                  },
                  "required": [
                    "key"
                  ]
                },
                "maxItems": 10
              },
              "interests": {
                "type": "object",
                "properties": {
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 25
                  },
                  "within_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1095
                  },
                  "min_strength": {
                    "anyOf": [
                      {
                        "type": "number",
                        "enum": [
                          1
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          2
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          3
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          4
                        ]
                      }
                    ]
                  },
                  "include_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "topic_ids"
                ],
                "additionalProperties": false
              },
              "interests_none": {
                "type": "object",
                "properties": {
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 25
                  },
                  "min_strength": {
                    "anyOf": [
                      {
                        "type": "number",
                        "enum": [
                          1
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          2
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          3
                        ]
                      },
                      {
                        "type": "number",
                        "enum": [
                          4
                        ]
                      }
                    ]
                  },
                  "include_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "topic_ids"
                ],
                "additionalProperties": false
              }
            },
            "additionalProperties": false
          },
          "kind": {
            "type": "string",
            "enum": [
              "dynamic",
              "static",
              "mixed",
              "all"
            ]
          },
          "include_count": {
            "type": "integer"
          },
          "exclude_count": {
            "type": "integer"
          },
          "labels": {
            "$ref": "#/components/schemas/AudienceLabels"
          },
          "usage": {
            "type": "object",
            "properties": {
              "campaigns_count": {
                "type": "integer"
              },
              "last_used_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "active_campaigns_count": {
                "type": "integer"
              },
              "active_campaigns": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "scheduled_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "status",
                    "scheduled_at"
                  ]
                }
              }
            },
            "required": [
              "campaigns_count",
              "last_used_at",
              "active_campaigns_count",
              "active_campaigns"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "include_contact_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "exclude_contact_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "filter",
          "kind",
          "include_count",
          "exclude_count",
          "labels",
          "usage",
          "created_by",
          "created_at",
          "updated_at",
          "include_contact_ids",
          "exclude_contact_ids"
        ]
      },
      "AudienceConditionData": {
        "type": "object",
        "properties": {
          "vehicle": {
            "type": "boolean"
          }
        },
        "required": [
          "vehicle"
        ]
      },
      "AudienceStats": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "total": {
            "type": "integer"
          },
          "whatsapp_reachable": {
            "type": "integer"
          },
          "email_reachable": {
            "type": "integer"
          },
          "computed_at": {
            "type": "string"
          },
          "partial": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "total",
          "whatsapp_reachable",
          "email_reachable",
          "computed_at",
          "partial"
        ]
      },
      "AudienceOverlap": {
        "type": "object",
        "properties": {
          "a": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "total"
            ]
          },
          "b": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "id",
              "total"
            ]
          },
          "both": {
            "type": "integer"
          },
          "a_only": {
            "type": "integer"
          },
          "b_only": {
            "type": "integer"
          }
        },
        "required": [
          "a",
          "b",
          "both",
          "a_only",
          "b_only"
        ]
      },
      "AudienceChannelReach": {
        "type": "object",
        "properties": {
          "reachable": {
            "type": "integer"
          },
          "no_identifier": {
            "type": "integer"
          },
          "invalid_identifier": {
            "type": "integer"
          },
          "duplicate": {
            "type": "integer"
          },
          "opted_out": {
            "type": "integer"
          },
          "opted_out_by": {
            "type": "object",
            "properties": {
              "stop_keyword": {
                "type": "integer"
              },
              "unsubscribe": {
                "type": "integer"
              },
              "bounce": {
                "type": "integer"
              },
              "complaint": {
                "type": "integer"
              },
              "manual": {
                "type": "integer"
              },
              "classified": {
                "type": "integer"
              },
              "scope_blocked": {
                "type": "integer"
              },
              "all_proactive_blocked": {
                "type": "integer"
              }
            }
          },
          "invalid_by": {
            "type": "object",
            "properties": {
              "too_short": {
                "type": "integer"
              },
              "landline": {
                "type": "integer"
              },
              "malformed": {
                "type": "integer"
              }
            }
          }
        },
        "required": [
          "reachable",
          "no_identifier",
          "invalid_identifier",
          "duplicate",
          "opted_out",
          "opted_out_by",
          "invalid_by"
        ]
      },
      "AudienceCounts": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "total": {
            "type": "integer"
          },
          "by_source": {
            "type": "object",
            "properties": {
              "rule": {
                "type": "integer"
              },
              "manual": {
                "type": "integer"
              },
              "rule_and_manual": {
                "type": "integer"
              }
            },
            "required": [
              "rule",
              "manual",
              "rule_and_manual"
            ]
          },
          "overrides": {
            "type": "object",
            "properties": {
              "included": {
                "type": "integer"
              },
              "included_not_member": {
                "type": "integer"
              },
              "excluded": {
                "type": "integer"
              },
              "excluded_would_match": {
                "type": "integer"
              }
            },
            "required": [
              "included",
              "included_not_member",
              "excluded",
              "excluded_would_match"
            ]
          },
          "omitted": {
            "type": "object",
            "properties": {
              "archived": {
                "type": "integer"
              },
              "blocked": {
                "type": "integer"
              },
              "spam": {
                "type": "integer"
              },
              "merged": {
                "type": "integer"
              },
              "import_only": {
                "type": "integer"
              },
              "excluded": {
                "type": "integer"
              }
            },
            "required": [
              "archived",
              "blocked",
              "spam",
              "merged",
              "import_only",
              "excluded"
            ]
          },
          "channels": {
            "type": "object",
            "properties": {
              "whatsapp": {
                "$ref": "#/components/schemas/AudienceChannelReach"
              },
              "email": {
                "$ref": "#/components/schemas/AudienceChannelReach"
              }
            },
            "required": [
              "whatsapp",
              "email"
            ]
          },
          "computed_at": {
            "type": "string"
          },
          "partial": {
            "type": "boolean"
          },
          "cap": {
            "type": "integer"
          },
          "whatsapp_eligible": {
            "type": "integer",
            "description": "TRANSITIONAL: channels.whatsapp.reachable + opted_out"
          },
          "whatsapp_suppressed": {
            "type": "integer",
            "description": "TRANSITIONAL: channels.whatsapp.opted_out"
          },
          "suppressed_is_partial": {
            "type": "boolean",
            "description": "TRANSITIONAL: = partial"
          }
        },
        "required": [
          "total",
          "by_source",
          "overrides",
          "omitted",
          "channels",
          "computed_at",
          "partial",
          "cap",
          "whatsapp_eligible",
          "whatsapp_suppressed",
          "suppressed_is_partial"
        ]
      },
      "AudienceOverridesResult": {
        "type": "object",
        "properties": {
          "applied": {
            "type": "integer"
          },
          "outcomes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "contact_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "input": {
                  "type": "string"
                },
                "result": {
                  "type": "string",
                  "enum": [
                    "added",
                    "removed",
                    "already",
                    "not_found",
                    "moderated",
                    "already_member_by_rule"
                  ]
                }
              },
              "required": [
                "contact_id",
                "result"
              ]
            }
          },
          "include_count": {
            "type": "integer"
          },
          "exclude_count": {
            "type": "integer"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "applied",
          "outcomes",
          "include_count",
          "exclude_count",
          "updated_at"
        ]
      },
      "MessagingBudget": {
        "type": "object",
        "properties": {
          "messaging_account_id": {
            "type": "string",
            "format": "uuid"
          },
          "account_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "This number plus the numbers counted with it (same WABA)."
          },
          "waba_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta's messaging-limit tier, e.g. TIER_250, TIER_2K."
          },
          "tier_unknown": {
            "type": "boolean",
            "description": "Never observed or unrecognized — treated as no cap."
          },
          "unlimited": {
            "type": "boolean"
          },
          "quality_rating": {
            "type": [
              "string",
              "null"
            ]
          },
          "tier_observed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "limit_per_24h": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Unique people per rolling 24 h; null = no cap."
          },
          "used_last_24h": {
            "type": "integer"
          },
          "used_by_campaigns_last_24h": {
            "type": "integer"
          },
          "used_by_other_senders_last_24h": {
            "type": "integer"
          },
          "remaining": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reserve": {
            "type": "integer",
            "description": "Kept for non-campaign senders: max(pct × limit, p95 of the daily non-campaign usage over 7 days), capped."
          },
          "reserve_basis": {
            "type": "object",
            "properties": {
              "pct_floor": {
                "type": "integer"
              },
              "observed_p95": {
                "type": "integer"
              }
            },
            "required": [
              "pct_floor",
              "observed_p95"
            ]
          },
          "campaign_cap": {
            "type": [
              "integer",
              "null"
            ]
          },
          "campaign_headroom": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What campaigns may still start right now."
          },
          "window_resets_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "next_batch_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "computed_at": {
            "type": "string"
          }
        },
        "required": [
          "messaging_account_id",
          "account_ids",
          "waba_id",
          "tier",
          "tier_unknown",
          "unlimited",
          "quality_rating",
          "tier_observed_at",
          "limit_per_24h",
          "used_last_24h",
          "used_by_campaigns_last_24h",
          "used_by_other_senders_last_24h",
          "remaining",
          "reserve",
          "reserve_basis",
          "campaign_cap",
          "campaign_headroom",
          "window_resets_at",
          "next_batch_at",
          "computed_at"
        ]
      },
      "CampaignSendWindow": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "timezone": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "IANA zone the window is read on, e.g. America/Santiago."
          },
          "days": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "minItems": 1,
            "maxItems": 7,
            "description": "ISO weekdays the window opens on: 1 = Monday … 7 = Sunday."
          },
          "start": {
            "type": "string",
            "description": "Local opening time, HH:MM (inclusive)."
          },
          "end": {
            "type": "string",
            "description": "Local closing time, HH:MM (exclusive); later than start."
          }
        },
        "required": [
          "timezone",
          "days",
          "start",
          "end"
        ],
        "additionalProperties": false,
        "description": "The window this plan used (resolved); null = contact hours only."
      },
      "CampaignSendSchedule": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "daily"
                ]
              },
              "timezone": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "IANA zone, e.g. America/Santiago."
              },
              "start_date": {
                "type": "string",
                "description": "First day (local), YYYY-MM-DD."
              },
              "time": {
                "type": "string",
                "description": "Local time of every batch, HH:MM."
              },
              "max_per_batch": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 1,
                "maximum": 100000,
                "description": "At most this many people per slot, in total across the slot’s grace, even with headroom left (ADR 0139 amendment) — e.g. 100 and 100 for an even A/B on a number that gives campaigns 200 a day. Absent/null = no cap."
              }
            },
            "required": [
              "kind",
              "timezone",
              "start_date",
              "time"
            ],
            "additionalProperties": false
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "slots"
                ]
              },
              "timezone": {
                "type": "string",
                "minLength": 1,
                "maxLength": 64,
                "description": "IANA zone, e.g. America/Santiago."
              },
              "slots": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string",
                      "description": "Local date, YYYY-MM-DD."
                    },
                    "time": {
                      "type": "string",
                      "description": "Local time, HH:MM."
                    }
                  },
                  "required": [
                    "date",
                    "time"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1,
                "maxItems": 60,
                "description": "The batches, one per instant (stored sorted ascending)."
              },
              "after_last": {
                "type": "string",
                "enum": [
                  "continue_daily",
                  "pause"
                ],
                "description": "Recipients left after the last slot: continue every day at the last slot’s time, or pause (schedule_exhausted)."
              },
              "max_per_batch": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 1,
                "maximum": 100000,
                "description": "At most this many people per slot, in total across the slot’s grace, even with headroom left (ADR 0139 amendment) — e.g. 100 and 100 for an even A/B on a number that gives campaigns 200 a day. Absent/null = no cap."
              }
            },
            "required": [
              "kind",
              "timezone",
              "slots",
              "after_last"
            ],
            "additionalProperties": false
          },
          {
            "type": "null"
          }
        ],
        "description": "The schedule this plan used (ADR 0139); null = none."
      },
      "CampaignScheduleSimulation": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "batches": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "at": {
                  "type": "string",
                  "description": "The slot instant (ISO)."
                },
                "local": {
                  "type": "string",
                  "description": "The slot on the schedule’s clock, e.g. «jue 15 oct, 20:00»."
                },
                "planned": {
                  "type": "integer",
                  "description": "People this batch would send to."
                },
                "slot_index": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Index into the sorted `slots`; null for a daily batch."
                },
                "warnings": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "enum": [
                      "budget_not_freed",
                      "partial_budget",
                      "outside_contact_hours",
                      "in_the_past"
                    ]
                  }
                },
                "shared_with": {
                  "type": "integer",
                  "description": "Slots the sibling campaigns of the number are expected to use in this batch’s 24 h window (at − 24 h, at]."
                }
              },
              "required": [
                "at",
                "local",
                "planned",
                "slot_index",
                "warnings",
                "shared_with"
              ]
            }
          },
          "covers_all": {
            "type": "boolean"
          },
          "remaining_after_last": {
            "type": "integer"
          },
          "completes_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The slot that sends the last person; null when it never does."
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "slot_in_past",
                    "duplicate_slot",
                    "too_close",
                    "uncovered",
                    "asap_not_allowed",
                    "shared_budget_hint"
                  ]
                },
                "slot_index": {
                  "type": "integer"
                },
                "message_es": {
                  "type": "string"
                },
                "message_en": {
                  "type": "string"
                },
                "suggested_at": {
                  "type": "string",
                  "description": "The earliest instant that would work (ISO)."
                }
              },
              "required": [
                "code",
                "message_es",
                "message_en"
              ]
            }
          },
          "tier_unknown": {
            "type": "boolean",
            "description": "No tier known for the number: no cap applied (all at the first slot)."
          },
          "replies_handled_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ai",
              "human"
            ],
            "description": "Who answers the replies: `ai` = the AI agent the number routes to (slots outside the contact hours carry no `outside_contact_hours` warning); `human` = the team; null = unknown (warned as for the team)."
          },
          "outside_contact_hours": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The batches (`local`) outside the workspace’s contact hours, whoever answers."
          },
          "siblings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "campaign_id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "scheduled",
                    "sending",
                    "paused"
                  ]
                },
                "pending": {
                  "type": "integer"
                },
                "schedule_label": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Its schedule; null = it sends continuously."
                },
                "estimated_per_day": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "About how many it sends per 24 h; null = no cap known on the number."
                }
              },
              "required": [
                "campaign_id",
                "name",
                "status",
                "pending",
                "schedule_label",
                "estimated_per_day"
              ]
            },
            "description": "The other campaigns of the number (same WABA group) — scheduled, sending or paused with people pending — sharing its limit, oldest first. Their expected batches count against this plan (ADR 0139 amendment)."
          }
        },
        "required": [
          "batches",
          "covers_all",
          "remaining_after_last",
          "completes_at",
          "issues",
          "tier_unknown",
          "replies_handled_by",
          "outside_contact_hours",
          "siblings"
        ],
        "description": "With a schedule: batch by batch, against the budget (ADR 0139). Null without one."
      },
      "CampaignSendPlan": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "recipients": {
            "type": "integer"
          },
          "sendable_now": {
            "type": [
              "integer",
              "null"
            ]
          },
          "fits_now": {
            "type": "boolean"
          },
          "per_window": {
            "type": [
              "integer",
              "null"
            ]
          },
          "windows": {
            "type": "integer",
            "description": "Sending days including the first (1 = it all fits now)."
          },
          "estimated_completion_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "recommended_send_mode": {
            "type": "string",
            "enum": [
              "paced",
              "asap"
            ],
            "description": "paced: continue automatically across the rolling 24 h windows; asap: send what fits now and pause (ADR 0129)."
          },
          "next_batch_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "send_window": {
            "$ref": "#/components/schemas/CampaignSendWindow"
          },
          "window_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. «lun–vie 10:00–18:00 (America/Santiago)»; null = none."
          },
          "next_window_opens_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when it is outside the window now: the first batch waits until then."
          },
          "send_schedule": {
            "$ref": "#/components/schemas/CampaignSendSchedule"
          },
          "schedule_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "e.g. «Todos los días a las 20:00 (America/Santiago) desde hoy»."
          },
          "schedule": {
            "$ref": "#/components/schemas/CampaignScheduleSimulation"
          }
        },
        "required": [
          "recipients",
          "sendable_now",
          "fits_now",
          "per_window",
          "windows",
          "estimated_completion_at",
          "recommended_send_mode",
          "next_batch_at",
          "send_window",
          "window_label",
          "next_window_opens_at"
        ]
      },
      "CampaignMessagingBudget": {
        "type": "object",
        "properties": {
          "budget": {
            "$ref": "#/components/schemas/MessagingBudget"
          },
          "plan": {
            "$ref": "#/components/schemas/CampaignSendPlan"
          }
        },
        "required": [
          "budget",
          "plan"
        ]
      },
      "CampaignHeaderMediaMeta": {
        "type": "object",
        "properties": {
          "updated_recipients": {
            "type": "integer",
            "minimum": 0,
            "description": "Recipients still `pending` whose frozen params now carry the new link. Already sent / delivered / read / failed ones are history and keep theirs."
          }
        },
        "required": [
          "updated_recipients"
        ]
      },
      "CampaignHeaderMediaBody": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "description": "Public https link to the image / video / PDF (e.g. the `url` `POST /whatsapp-templates/header-media` returns).",
            "example": "https://media.example.com/promo-noviembre.jpg"
          }
        },
        "required": [
          "url"
        ],
        "additionalProperties": false
      },
      "CampaignSendNowResult": {
        "type": "object",
        "properties": {
          "started": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "waiting_for_window_until": {
            "type": [
              "string",
              "null"
            ],
            "description": "Outside the send window: nothing leaves until this instant (the window opening). Null = sends now."
          }
        },
        "required": [
          "started",
          "waiting_for_window_until"
        ]
      },
      "CampaignSendNowBody": {
        "type": "object",
        "properties": {
          "send_schedule": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CampaignSendSchedule"
              },
              {
                "description": "When paced batches leave, as explicit instants (WhatsApp, ADR 0139). Replaces the send window when set; null = the send window / contact hours. Refused with an `asap` send_mode (400 schedule_requires_paced)."
              }
            ]
          }
        },
        "additionalProperties": false
      },
      "CampaignPreflight": {
        "type": "object",
        "properties": {
          "ready": {
            "type": "boolean",
            "description": "No check is `block`."
          },
          "checks": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "template_approved | recipients | header_media | test_sent | messaging_tier | number_quality | schedule | replies | opt_out | recent_campaign_overlap (WhatsApp); template | recipients | unsubscribe | test_sent | sender_domain (email)."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "ok",
                    "warn",
                    "block"
                  ]
                },
                "title_es": {
                  "type": "string"
                },
                "title_en": {
                  "type": "string"
                },
                "detail_es": {
                  "type": "string"
                },
                "detail_en": {
                  "type": "string"
                },
                "href": {
                  "type": "string",
                  "description": "App-relative link to fix it, e.g. the template or the audience."
                }
              },
              "required": [
                "key",
                "status",
                "title_es",
                "title_en"
              ]
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "recipients": {
                "type": "integer"
              },
              "channel": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "email"
                ]
              },
              "template_name": {
                "type": "string"
              },
              "schedule_label_es": {
                "type": "string"
              },
              "schedule_label_en": {
                "type": "string"
              },
              "first_batch_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "completes_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "batches_count": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "limit_per_24h": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The number’s Meta limit (WhatsApp); null = no cap known."
              },
              "reserve": {
                "type": "integer",
                "description": "Kept free for reminders and other senders (WhatsApp)."
              },
              "replies_handled_by": {
                "type": "string",
                "enum": [
                  "ai",
                  "human"
                ],
                "description": "Who answers the replies (WhatsApp): the AI agent the number routes to, or the team (`reply_mode = human`, or no agent on the number)."
              }
            },
            "required": [
              "recipients",
              "channel",
              "schedule_label_es",
              "schedule_label_en",
              "first_batch_at",
              "completes_at",
              "batches_count"
            ]
          },
          "plan": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CampaignScheduleSimulation"
              },
              {
                "description": "«Ver plan»: the schedule simulated; null without a schedule."
              }
            ]
          }
        },
        "required": [
          "ready",
          "checks",
          "summary",
          "plan"
        ]
      },
      "CampaignResumeBody": {
        "type": "object",
        "properties": {
          "send_mode": {
            "type": "string",
            "enum": [
              "paced",
              "asap"
            ],
            "description": "paced: continue automatically across the rolling 24 h windows; asap: send what fits now and pause (ADR 0129)."
          },
          "acknowledge_quality": {
            "type": "boolean",
            "description": "Resume a WhatsApp campaign although its number's quality is not GREEN (YELLOW or RED, or unknown after a `number_quality` pause). Without it such a resume answers 409 with `details.reason: number_quality_not_green` and `details.quality_rating`."
          }
        },
        "additionalProperties": false
      },
      "CampaignRecipient": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "campaign_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "identifier": {
            "type": "string"
          },
          "params": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "rendered_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "read",
              "failed",
              "suppressed"
            ]
          },
          "external_message_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "title": {
                "type": "string"
              },
              "detail": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "code",
              "title",
              "detail"
            ],
            "description": "Meta's raw error, as it answered."
          },
          "error_reason": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "human_es": {
                "type": "string"
              },
              "human_en": {
                "type": "string"
              },
              "action": {
                "type": "string",
                "enum": [
                  "none",
                  "remove_number",
                  "retry_later",
                  "check_template"
                ],
                "description": "none: Meta decided, nothing to do; remove_number: the number cannot receive WhatsApp — take it out of the audience; retry_later: a limit or transient error; check_template: the template/variables/media must be fixed."
              }
            },
            "required": [
              "code",
              "human_es",
              "human_en",
              "action"
            ],
            "description": "The failure in plain language, with a suggested action."
          },
          "replied_button": {
            "type": [
              "string",
              "null"
            ],
            "description": "The template quick-reply button the person's first reply tapped; null when they wrote free text or did not reply."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "replied_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "replied_conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The conversation the send was stored in — created by the send (born closed) or the live thread it was appended to. Null before the send, or for a send the backfill has not linked yet."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "campaign_id",
          "contact_id",
          "contact_name",
          "identifier",
          "params",
          "rendered_text",
          "state",
          "external_message_id",
          "error",
          "error_reason",
          "replied_button",
          "sent_at",
          "delivered_at",
          "read_at",
          "failed_at",
          "replied_at",
          "replied_conversation_id",
          "created_at"
        ]
      },
      "CampaignResults": {
        "type": "object",
        "properties": {
          "campaign_id": {
            "type": "string",
            "format": "uuid"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "email",
              "voice"
            ]
          },
          "funnel": {
            "type": "object",
            "properties": {
              "recipients": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "read": {
                "type": "integer"
              },
              "replied": {
                "type": "integer"
              },
              "opted_out": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "suppressed": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              },
              "conversations_created": {
                "type": "integer",
                "description": "Conversations this campaign's sends created («conversaciones iniciadas»). A send to a contact with a live thread is appended there and not counted."
              }
            },
            "required": [
              "recipients",
              "sent",
              "delivered",
              "read",
              "replied",
              "opted_out",
              "failed",
              "suppressed",
              "pending",
              "conversations_created"
            ]
          },
          "rates": {
            "type": "object",
            "properties": {
              "delivered": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "delivered / sent"
              },
              "read": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "read / delivered"
              },
              "replied": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "replied / delivered"
              }
            },
            "required": [
              "delivered",
              "read",
              "replied"
            ],
            "description": "0–1 fractions; null when the denominator is 0."
          },
          "buttons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              },
              "required": [
                "text",
                "count"
              ]
            },
            "description": "Replies whose first message was a tap on one of the template's quick-reply buttons, per button (every button listed, zeros included)."
          },
          "free_text": {
            "type": "integer",
            "description": "Replies whose first message was not a button tap."
          },
          "failures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "human_es": {
                  "type": "string"
                },
                "human_en": {
                  "type": "string"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "none",
                    "remove_number",
                    "retry_later",
                    "check_template"
                  ],
                  "description": "none: Meta decided, nothing to do; remove_number: the number cannot receive WhatsApp — take it out of the audience; retry_later: a limit or transient error; check_template: the template/variables/media must be fixed."
                },
                "count": {
                  "type": "integer"
                }
              },
              "required": [
                "code",
                "title",
                "human_es",
                "human_en",
                "action",
                "count"
              ]
            }
          },
          "opt_outs": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "by_reason": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "count",
              "by_reason"
            ]
          },
          "timeline": {
            "type": "object",
            "properties": {
              "unit": {
                "type": "string",
                "enum": [
                  "hour",
                  "day"
                ]
              },
              "timezone": {
                "type": "string"
              },
              "points": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "at": {
                      "type": "string",
                      "description": "Bucket start (ISO, UTC)."
                    },
                    "sent": {
                      "type": "integer"
                    },
                    "delivered": {
                      "type": "integer"
                    },
                    "read": {
                      "type": "integer"
                    },
                    "replied": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "at",
                    "sent",
                    "delivered",
                    "read",
                    "replied"
                  ]
                }
              }
            },
            "required": [
              "unit",
              "timezone",
              "points"
            ]
          }
        },
        "required": [
          "campaign_id",
          "channel",
          "funnel",
          "rates",
          "buttons",
          "free_text",
          "failures",
          "opt_outs",
          "timeline"
        ]
      },
      "CampaignMetaInsights": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ready"
                ]
              },
              "template": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "language": {
                    "type": "string"
                  },
                  "external_id": {
                    "type": "string",
                    "description": "Meta template id."
                  }
                },
                "required": [
                  "name",
                  "language",
                  "external_id"
                ]
              },
              "range": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string",
                    "description": "First UTC day (YYYY-MM-DD), inclusive."
                  },
                  "end": {
                    "type": "string",
                    "description": "Last UTC day (YYYY-MM-DD), inclusive."
                  }
                },
                "required": [
                  "start",
                  "end"
                ]
              },
              "totals": {
                "type": "object",
                "properties": {
                  "sent": {
                    "type": "integer"
                  },
                  "delivered": {
                    "type": "integer"
                  },
                  "read": {
                    "type": "integer"
                  },
                  "cost": {
                    "type": "object",
                    "properties": {
                      "amount": {
                        "type": "string",
                        "description": "Decimal string in `currency`."
                      },
                      "currency": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "amount",
                      "currency"
                    ]
                  }
                },
                "required": [
                  "sent",
                  "delivered",
                  "read",
                  "cost"
                ]
              },
              "clicks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string",
                      "description": "Meta button type: `quick_reply_button`, `url_button`, …"
                    },
                    "button": {
                      "type": "string",
                      "description": "Button text."
                    },
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "type",
                    "button",
                    "count"
                  ]
                },
                "description": "Clicks per button over the range, most-clicked first."
              },
              "daily": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string",
                      "description": "UTC day."
                    },
                    "sent": {
                      "type": "integer"
                    },
                    "delivered": {
                      "type": "integer"
                    },
                    "read": {
                      "type": "integer"
                    },
                    "clicks": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "date",
                    "sent",
                    "delivered",
                    "read",
                    "clicks"
                  ]
                }
              },
              "fetched_at": {
                "type": "string",
                "description": "When Meta was read (cached up to 15 min)."
              }
            },
            "required": [
              "status",
              "template",
              "range",
              "totals",
              "clicks",
              "daily",
              "fetched_at"
            ]
          },
          {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "enabling"
                ]
              },
              "retry_after_seconds": {
                "type": "integer",
                "description": "Meta is switching the account’s template insights on; ask again after this."
              }
            },
            "required": [
              "status",
              "retry_after_seconds"
            ]
          }
        ]
      },
      "CampaignTestSendResult": {
        "type": "object",
        "properties": {
          "campaign_id": {
            "type": "string",
            "format": "uuid"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "email"
            ]
          },
          "header_media_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp: the header media every recipient got."
          },
          "remaining_this_hour": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Test messages this campaign may still send this hour."
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "phone": {
                  "type": "string"
                },
                "email": {
                  "type": "string"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "sent",
                    "failed"
                  ]
                },
                "provider_message_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "error_code": {
                  "type": "string",
                  "enum": [
                    "invalid_phone",
                    "landline",
                    "invalid_email",
                    "duplicate",
                    "opted_out",
                    "contact_blocked",
                    "safety_hold",
                    "missing_variables",
                    "email_not_verified",
                    "send_failed"
                  ]
                },
                "error_message": {
                  "type": "string"
                },
                "provider_code": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "opt_out_reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "missing": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "conversation_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "contact_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "rendered_text": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "values": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "object",
                    "properties": {
                      "value": {
                        "type": "string"
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "variable",
                          "contact_field",
                          "static",
                          "fallback",
                          "example",
                          "empty"
                        ]
                      }
                    },
                    "required": [
                      "value",
                      "source"
                    ]
                  }
                }
              },
              "required": [
                "name",
                "status"
              ]
            }
          }
        },
        "required": [
          "campaign_id",
          "channel",
          "header_media_url",
          "remaining_this_hour",
          "results"
        ]
      },
      "CampaignTestSendRecipient": {
        "type": "object",
        "properties": {
          "phone": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "format": "email"
          },
          "name": {
            "type": "string",
            "maxLength": 120
          },
          "variables": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 1000
            }
          }
        },
        "additionalProperties": false
      },
      "CampaignTestSendBody": {
        "type": "object",
        "properties": {
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CampaignTestSendRecipient"
            },
            "minItems": 1,
            "maxItems": 5
          }
        },
        "required": [
          "recipients"
        ],
        "additionalProperties": false
      },
      "ClinicWaitlistDemand": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "days": {
                "type": "integer"
              },
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "days",
              "from",
              "to"
            ]
          },
          "totals": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": "integer",
                "description": "Distinct patients with a live entry now (waiting, or holding an offer)."
              },
              "joined": {
                "type": "integer",
                "description": "Distinct patients who joined the waitlist inside the window."
              },
              "booked": {
                "type": "integer",
                "description": "Distinct patients whose entry became a booking inside the window."
              }
            },
            "required": [
              "waiting_now",
              "joined",
              "booked"
            ]
          },
          "by_specialty": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "specialty": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The professional's specialty as the roster spells it; null = «whoever is free» or no specialty on file."
                },
                "label": {
                  "type": "string",
                  "description": "Ready to print: the specialty, «Cualquier profesional» or «Sin especialidad»."
                },
                "waiting_now": {
                  "type": "integer",
                  "description": "Distinct patients with a live entry now (waiting, or holding an offer)."
                },
                "joined": {
                  "type": "integer",
                  "description": "Distinct patients who joined the waitlist inside the window."
                },
                "booked": {
                  "type": "integer",
                  "description": "Distinct patients whose entry became a booking inside the window."
                },
                "professionals": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "professional_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid"
                      },
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "waiting_now": {
                        "type": "integer",
                        "description": "Distinct patients with a live entry now (waiting, or holding an offer)."
                      },
                      "joined": {
                        "type": "integer",
                        "description": "Distinct patients who joined the waitlist inside the window."
                      },
                      "booked": {
                        "type": "integer",
                        "description": "Distinct patients whose entry became a booking inside the window."
                      }
                    },
                    "required": [
                      "professional_id",
                      "name",
                      "waiting_now",
                      "joined",
                      "booked"
                    ]
                  }
                }
              },
              "required": [
                "specialty",
                "label",
                "waiting_now",
                "joined",
                "booked",
                "professionals"
              ]
            }
          }
        },
        "required": [
          "window",
          "totals",
          "by_specialty"
        ]
      },
      "ClinicProduccionReport": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "professional_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "basis": {
            "type": "string",
            "enum": [
              "service_lines",
              "catalog_price",
              "mixed",
              "none",
              "mirror_lines"
            ],
            "description": "Which arm produced the money. `mirror_lines`: a Dentalink / Medilink clinic — the vendor's plan lines marked realizado, by fecha de realización, at the line's total (exact). `service_lines`: the cita's frozen Vitrina lines (a native clinic). `catalog_price`: the catálogo's current price for the prestación a mirrored cita names (an estimate). `mixed`: both. `none`: nothing priced anything."
          },
          "totals": {
            "type": "object",
            "properties": {
              "attended": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Attended citas in the window. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "lines": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "visits": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`mirror_lines` only: distinct visits that produced (patient × day) — what the per-professional `appointments` count, so a table total sums its rows. `null` on the native arms (there `attended` is the total). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "lines_without_price": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "performed_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "collected_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`payment_allocation.appointment`: money the ledger allocated to these citas. `mirror_payment.received`: every live payment received in the window, by fecha de recepción; `null` with a professional filter (a payment names no professional)."
              },
              "collected_basis": {
                "type": "string",
                "enum": [
                  "payment_allocation.appointment",
                  "mirror_payment.received"
                ]
              }
            },
            "required": [
              "attended",
              "lines",
              "visits",
              "lines_without_price",
              "performed_clp",
              "collected_clp",
              "collected_basis"
            ]
          },
          "by_professional": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "The professional / prestación id, or the categoría text; `__none__` when the line names none."
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "appointments": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Citas that produced (`mirror_lines`: visits, patient × day). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "lines": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Prestaciones performed. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "performed_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                }
              },
              "required": [
                "key",
                "label",
                "appointments",
                "lines",
                "performed_clp"
              ]
            },
            "description": "Per professional who PERFORMED the work (not who quoted the plan), most CLP first."
          },
          "by_service": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "The professional / prestación id, or the categoría text; `__none__` when the line names none."
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "appointments": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Citas that produced (`mirror_lines`: visits, patient × day). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "lines": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Prestaciones performed. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "performed_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                }
              },
              "required": [
                "key",
                "label",
                "appointments",
                "lines",
                "performed_clp"
              ]
            }
          },
          "by_category": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "The professional / prestación id, or the categoría text; `__none__` when the line names none."
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "appointments": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Citas that produced (`mirror_lines`: visits, patient × day). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "lines": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Prestaciones performed. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "performed_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                }
              },
              "required": [
                "key",
                "label",
                "appointments",
                "lines",
                "performed_clp"
              ]
            },
            "description": "`clinic_service.categoria` is free text: the label is the key."
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "`YYYY-MM`."
                },
                "attended": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "performed_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "collected_clp": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              },
              "required": [
                "month",
                "attended",
                "performed_clp",
                "collected_clp"
              ]
            }
          },
          "suppression": {
            "type": "object",
            "properties": {
              "min_count": {
                "type": "integer"
              },
              "filters_ignored": {
                "type": "boolean"
              },
              "withheld_rows": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "min_count",
              "filters_ignored",
              "withheld_rows"
            ],
            "description": "Present only for a caller without `clinic_money:read`."
          }
        },
        "required": [
          "from",
          "to",
          "location_id",
          "professional_id",
          "basis",
          "totals",
          "by_professional",
          "by_service",
          "by_category",
          "months"
        ]
      },
      "ClinicIngresosReport": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "today": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "timezone": {
            "type": "string"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "professional_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "origin": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "meta",
              "organic",
              "unknown"
            ],
            "description": "The Origen filter applied; `null` = everyone."
          },
          "state": {
            "type": "string",
            "enum": [
              "ready",
              "unavailable"
            ]
          },
          "state_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "mirror",
                  "native",
                  "none"
                ],
                "description": "`native`: Vitrina's own agenda and caja (a native clinic). `mirror`: the Dentalink / Medilink revenue mirror. `none`: neither (`state: unavailable`)."
              },
              "synced_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "synced_at"
            ]
          },
          "rules": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "hoy": {
            "type": "object",
            "properties": {
              "day": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "produccion_clp": {
                "type": "integer"
              },
              "prestaciones": {
                "type": "integer"
              },
              "presupuestado_clp": {
                "type": "integer"
              },
              "presupuestos": {
                "type": "integer"
              },
              "recaudado_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "pagos": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "citas": {
                "type": "integer",
                "description": "Citas on the agenda today, cancelled ones out. 0 = the clinic is not seeing patients today (closed, holiday), so three $0 figures are not a slow day."
              }
            },
            "required": [
              "day",
              "produccion_clp",
              "prestaciones",
              "presupuestado_clp",
              "presupuestos",
              "recaudado_clp",
              "pagos",
              "citas"
            ]
          },
          "totals": {
            "type": "object",
            "properties": {
              "produccion": {
                "type": "object",
                "properties": {
                  "total_clp": {
                    "type": "integer"
                  },
                  "nuevos_clp": {
                    "type": "integer",
                    "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                  },
                  "tratamiento_clp": {
                    "type": "integer"
                  },
                  "prestaciones": {
                    "type": "integer"
                  }
                },
                "required": [
                  "total_clp",
                  "nuevos_clp",
                  "tratamiento_clp",
                  "prestaciones"
                ]
              },
              "presupuestado": {
                "type": "object",
                "properties": {
                  "total_clp": {
                    "type": "integer"
                  },
                  "nuevos_clp": {
                    "type": "integer",
                    "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                  },
                  "tratamiento_clp": {
                    "type": "integer"
                  },
                  "presupuestos": {
                    "type": "integer"
                  }
                },
                "required": [
                  "total_clp",
                  "nuevos_clp",
                  "tratamiento_clp",
                  "presupuestos"
                ]
              },
              "recaudacion": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "total_clp": {
                    "type": "integer"
                  },
                  "nuevos_clp": {
                    "type": "integer",
                    "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                  },
                  "tratamiento_clp": {
                    "type": "integer"
                  },
                  "pagos": {
                    "type": "integer"
                  }
                },
                "required": [
                  "total_clp",
                  "nuevos_clp",
                  "tratamiento_clp",
                  "pagos"
                ],
                "description": "`null` with `professional_id`: a payment names no professional."
              },
              "by_origen": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "meta": {
                    "type": "object",
                    "properties": {
                      "produccion_clp": {
                        "type": "integer"
                      },
                      "presupuestado_clp": {
                        "type": "integer"
                      },
                      "recaudado_clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "produccion_clp",
                      "presupuestado_clp",
                      "recaudado_clp"
                    ]
                  },
                  "organic": {
                    "type": "object",
                    "properties": {
                      "produccion_clp": {
                        "type": "integer"
                      },
                      "presupuestado_clp": {
                        "type": "integer"
                      },
                      "recaudado_clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "produccion_clp",
                      "presupuestado_clp",
                      "recaudado_clp"
                    ]
                  },
                  "unknown": {
                    "type": "object",
                    "properties": {
                      "produccion_clp": {
                        "type": "integer"
                      },
                      "presupuestado_clp": {
                        "type": "integer"
                      },
                      "recaudado_clp": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "produccion_clp",
                      "presupuestado_clp",
                      "recaudado_clp"
                    ]
                  }
                },
                "description": "Producción, presupuestado and recaudación by Origen (D77): `meta`, `organic`, `unknown` («Sin dato»). `recaudado_clp` is `null` with `professional_id`. The whole object is `null` when the Origen could not be read (never guessed as `unknown`)."
              }
            },
            "required": [
              "produccion",
              "presupuestado",
              "recaudacion",
              "by_origen"
            ]
          },
          "days": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "produccion": {
                  "type": "object",
                  "properties": {
                    "total_clp": {
                      "type": "integer"
                    },
                    "nuevos_clp": {
                      "type": "integer",
                      "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                    },
                    "tratamiento_clp": {
                      "type": "integer"
                    },
                    "prestaciones": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "total_clp",
                    "nuevos_clp",
                    "tratamiento_clp",
                    "prestaciones"
                  ]
                },
                "presupuestado": {
                  "type": "object",
                  "properties": {
                    "total_clp": {
                      "type": "integer"
                    },
                    "nuevos_clp": {
                      "type": "integer",
                      "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                    },
                    "tratamiento_clp": {
                      "type": "integer"
                    },
                    "presupuestos": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "total_clp",
                    "nuevos_clp",
                    "tratamiento_clp",
                    "presupuestos"
                  ]
                },
                "recaudacion": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "total_clp": {
                      "type": "integer"
                    },
                    "nuevos_clp": {
                      "type": "integer",
                      "description": "Patients whose primera visita (Nuevo / Nuevo · reagenda) starts in the same month."
                    },
                    "tratamiento_clp": {
                      "type": "integer"
                    },
                    "pagos": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "total_clp",
                    "nuevos_clp",
                    "tratamiento_clp",
                    "pagos"
                  ],
                  "description": "`null` with `professional_id`: a payment names no professional."
                },
                "by_origen": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "meta": {
                      "type": "object",
                      "properties": {
                        "produccion_clp": {
                          "type": "integer"
                        },
                        "presupuestado_clp": {
                          "type": "integer"
                        },
                        "recaudado_clp": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "produccion_clp",
                        "presupuestado_clp",
                        "recaudado_clp"
                      ]
                    },
                    "organic": {
                      "type": "object",
                      "properties": {
                        "produccion_clp": {
                          "type": "integer"
                        },
                        "presupuestado_clp": {
                          "type": "integer"
                        },
                        "recaudado_clp": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "produccion_clp",
                        "presupuestado_clp",
                        "recaudado_clp"
                      ]
                    },
                    "unknown": {
                      "type": "object",
                      "properties": {
                        "produccion_clp": {
                          "type": "integer"
                        },
                        "presupuestado_clp": {
                          "type": "integer"
                        },
                        "recaudado_clp": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "produccion_clp",
                        "presupuestado_clp",
                        "recaudado_clp"
                      ]
                    }
                  },
                  "description": "Producción, presupuestado and recaudación by Origen (D77): `meta`, `organic`, `unknown` («Sin dato»). `recaudado_clp` is `null` with `professional_id`. The whole object is `null` when the Origen could not be read (never guessed as `unknown`)."
                },
                "day": {
                  "type": "string",
                  "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                }
              },
              "required": [
                "produccion",
                "presupuestado",
                "recaudacion",
                "by_origen",
                "day"
              ]
            }
          },
          "clinic": {
            "type": "object",
            "properties": {
              "produccion_clp": {
                "type": "integer",
                "description": "Performed by this doctor (the performer)."
              },
              "prestaciones": {
                "type": "integer"
              },
              "presupuestos": {
                "type": "integer",
                "description": "Real presupuestos issued ($0 and evaluación-only out), by the quoting doctor."
              },
              "presupuestado_clp": {
                "type": "integer",
                "description": "Their value without evaluación lines."
              },
              "aceptados": {
                "type": "integer"
              },
              "aceptacion_pct": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "aceptado_clp": {
                "type": "integer"
              },
              "abonado_clp": {
                "type": "integer",
                "description": "Already paid on the plans issued in the period."
              },
              "nuevos_presentados": {
                "type": "integer"
              },
              "nuevos_aceptados": {
                "type": "integer"
              }
            },
            "required": [
              "produccion_clp",
              "prestaciones",
              "presupuestos",
              "presupuestado_clp",
              "aceptados",
              "aceptacion_pct",
              "aceptado_clp",
              "abonado_clp",
              "nuevos_presentados",
              "nuevos_aceptados"
            ]
          },
          "by_professional": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "professional_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "professional_name": {
                  "type": "string"
                },
                "produccion_clp": {
                  "type": "integer",
                  "description": "Performed by this doctor (the performer)."
                },
                "prestaciones": {
                  "type": "integer"
                },
                "presupuestos": {
                  "type": "integer",
                  "description": "Real presupuestos issued ($0 and evaluación-only out), by the quoting doctor."
                },
                "presupuestado_clp": {
                  "type": "integer",
                  "description": "Their value without evaluación lines."
                },
                "aceptados": {
                  "type": "integer"
                },
                "aceptacion_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "aceptado_clp": {
                  "type": "integer"
                },
                "abonado_clp": {
                  "type": "integer",
                  "description": "Already paid on the plans issued in the period."
                },
                "nuevos_presentados": {
                  "type": "integer"
                },
                "nuevos_aceptados": {
                  "type": "integer"
                }
              },
              "required": [
                "professional_id",
                "professional_name",
                "produccion_clp",
                "prestaciones",
                "presupuestos",
                "presupuestado_clp",
                "aceptados",
                "aceptacion_pct",
                "aceptado_clp",
                "abonado_clp",
                "nuevos_presentados",
                "nuevos_aceptados"
              ]
            }
          }
        },
        "required": [
          "from",
          "to",
          "today",
          "timezone",
          "location_id",
          "professional_id",
          "origin",
          "state",
          "state_reason",
          "source",
          "rules",
          "hoy",
          "totals",
          "days",
          "clinic",
          "by_professional"
        ]
      },
      "ClinicIngresosMonths": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "state": {
            "type": "string",
            "enum": [
              "ready",
              "unavailable"
            ]
          },
          "rules": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "`YYYY-MM`."
                },
                "produccion_clp": {
                  "type": "integer"
                },
                "presupuestos": {
                  "type": "integer"
                },
                "presupuestado_clp": {
                  "type": "integer"
                },
                "aceptacion_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "aceptado_clp": {
                  "type": "integer"
                },
                "recaudacion_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "month",
                "produccion_clp",
                "presupuestos",
                "presupuestado_clp",
                "aceptacion_pct",
                "aceptado_clp",
                "recaudacion_clp"
              ]
            }
          },
          "professionals": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "professional_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "professional_name": {
                  "type": "string"
                },
                "months": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "month": {
                        "type": "string",
                        "description": "`YYYY-MM`."
                      },
                      "produccion_clp": {
                        "type": "integer"
                      },
                      "presupuestos": {
                        "type": "integer"
                      },
                      "presupuestado_clp": {
                        "type": "integer"
                      },
                      "aceptacion_pct": {
                        "type": [
                          "number",
                          "null"
                        ]
                      },
                      "aceptado_clp": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "month",
                      "produccion_clp",
                      "presupuestos",
                      "presupuestado_clp",
                      "aceptacion_pct",
                      "aceptado_clp"
                    ]
                  }
                }
              },
              "required": [
                "professional_id",
                "professional_name",
                "months"
              ]
            }
          }
        },
        "required": [
          "from",
          "to",
          "state",
          "rules",
          "months",
          "professionals"
        ]
      },
      "ClinicIngresosDay": {
        "type": "object",
        "properties": {
          "day": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "identity": {
            "type": "boolean"
          },
          "plans": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "plan_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "patient": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ],
                  "description": "`name` only for a caller allowed to see patient identity (`contacts:read` + `clinic:read`, plus `clinic_patients:read` for an API credential). A caller on `clinic_insights:read` alone gets `id` and `name` both `null`."
                },
                "professional_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "professional_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "total_clp": {
                  "type": "integer"
                },
                "abonado_clp": {
                  "type": "integer"
                },
                "deuda_clp": {
                  "type": "integer"
                },
                "tipo": {
                  "type": "string",
                  "enum": [
                    "nuevo",
                    "tratamiento"
                  ]
                },
                "origen": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "meta",
                    "organic",
                    "unknown"
                  ]
                },
                "aceptado": {
                  "type": [
                    "boolean",
                    "null"
                  ],
                  "description": "Números comerciales' verdict; `null` for a $0 or evaluación-only plan."
                }
              },
              "required": [
                "plan_id",
                "patient",
                "professional_id",
                "professional_name",
                "total_clp",
                "abonado_clp",
                "deuda_clp",
                "tipo",
                "origen",
                "aceptado"
              ]
            }
          },
          "payments": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "payment_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "time": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "patient": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name"
                  ],
                  "description": "`name` only for a caller allowed to see patient identity (`contacts:read` + `clinic:read`, plus `clinic_patients:read` for an API credential). A caller on `clinic_insights:read` alone gets `id` and `name` both `null`."
                },
                "amount_clp": {
                  "type": "integer"
                },
                "tipo": {
                  "type": "string",
                  "enum": [
                    "nuevo",
                    "tratamiento"
                  ]
                },
                "origen": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "meta",
                    "organic",
                    "unknown"
                  ]
                }
              },
              "required": [
                "payment_id",
                "time",
                "patient",
                "amount_clp",
                "tipo",
                "origen"
              ]
            }
          },
          "produccion": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "professional_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "professional_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "prestaciones": {
                  "type": "integer"
                },
                "produccion_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "professional_id",
                "professional_name",
                "prestaciones",
                "produccion_clp"
              ]
            }
          }
        },
        "required": [
          "day",
          "identity",
          "plans",
          "payments",
          "produccion"
        ]
      },
      "AgendaResults": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "timezone": {
            "type": "string"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "family": {
            "type": "string",
            "enum": [
              "clinic",
              "dealer",
              "generic"
            ],
            "description": "Vocabulary: citas/pacientes, visitas/clientes, citas/clientes."
          },
          "previous_period": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "bookings": {
            "type": "object",
            "properties": {
              "ai": {
                "type": "integer",
                "description": "Citas of the period booked by Vitrina's agent in a conversation (`source='agent'`, not a public booking page)."
              },
              "agenda_total": {
                "type": "integer",
                "description": "Every cita of the period (blocks and holds excluded)."
              },
              "share_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "ai_attended": {
                "type": "integer",
                "description": "Of `ai`: citas the patient attended."
              },
              "ai_missed": {
                "type": "integer",
                "description": "Of `ai`: cancelled (with or without notice) or no-show."
              },
              "ai_upcoming": {
                "type": "integer",
                "description": "Agent-booked citas dated after `to` — booked, not happened yet. Not part of `ai`."
              },
              "first_ai_booked_on": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
                "description": "Day (clinic zone) the agent booked its first cita ever. A window that starts earlier has no agent bookings before this day."
              },
              "previous": {
                "type": "object",
                "properties": {
                  "ai": {
                    "type": "integer"
                  },
                  "agenda_total": {
                    "type": "integer"
                  },
                  "share_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  }
                },
                "required": [
                  "ai",
                  "agenda_total",
                  "share_pct"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "ai",
              "agenda_total",
              "share_pct",
              "ai_attended",
              "ai_missed",
              "ai_upcoming",
              "first_ai_booked_on",
              "previous",
              "rule"
            ]
          },
          "conversation_rate": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "measured",
                  "not_booking_yet",
                  "unavailable"
                ],
                "description": "`not_booking_yet`: the agent had not booked any cita by `to`; `unavailable`: the read failed. Both leave every figure `null`."
              },
              "from": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
                "description": "First day actually measured: the page `from`, or the day Vitrina first booked when that is later."
              },
              "to": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "starts_at_first_booking": {
                "type": "boolean",
                "description": "True when `from` was moved forward to the first AI booking (conversations before it could not be booked by the AI)."
              },
              "conversations": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Denominator — conversations Vitrina answered ALONE in the window with NEW people (they had not written before the window — D94; the others are in `existing`): the person wrote, the AI agent replied after that message, and no team member (`human_user`) wrote before the AI booking (or in the whole window, with no booking). Tool calls, reasoning and events are not replies; an AI follow-up nobody answered is not counted. Quarantined conversations, system senders and team accounts are out. Every sucursal (conversations carry no location)."
              },
              "booked_conversations": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Numerator — of `conversations`, those where the agent booked at least one cita (`source=agent`, not a public booking page; blocks, holds and anuladas out) BOOKED inside the window."
              },
              "ai_bookings": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Citas the agent booked in those conversations (a conversation may book more than one). Counted by the day they were booked, unlike `bookings.ai` (by the day of the cita)."
              },
              "rate_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "`booked_conversations ÷ conversations`, percent with one decimal."
              },
              "team_helped": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Conversations the AI answered where a team member wrote before the outcome — excluded from the rate."
              },
              "existing": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "conversations": {
                    "type": "integer",
                    "description": "Conversations Vitrina answered alone with people who had written to the business BEFORE the window (patients’ follow-ups, changes of hour, earlier leads)."
                  },
                  "booked_conversations": {
                    "type": "integer",
                    "description": "Of them, those where the agent booked at least one cita (anuladas excluded)."
                  },
                  "ai_bookings": {
                    "type": "integer",
                    "description": "Citas the agent booked in them."
                  }
                },
                "required": [
                  "conversations",
                  "booked_conversations",
                  "ai_bookings"
                ],
                "description": "D94: conversations of people who had written to the business BEFORE the window (follow-ups, changes of hour) — outside the rate, said on their own line. `null` when not measured."
              },
              "cancelled": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Citas the agent booked for new people in the window that were then anuladas — never in `ai_bookings`. `null` when not measured."
              },
              "truncated": {
                "type": "boolean",
                "description": "The read hit its row cap: counts are a floor."
              },
              "previous": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "conversations": {
                    "type": "integer"
                  },
                  "booked_conversations": {
                    "type": "integer"
                  },
                  "rate_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  }
                },
                "required": [
                  "from",
                  "to",
                  "conversations",
                  "booked_conversations",
                  "rate_pct"
                ],
                "description": "The equal-length window right before, only when the AI was already booking through all of it; `null` otherwise (no fair baseline)."
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "state",
              "from",
              "to",
              "starts_at_first_booking",
              "conversations",
              "booked_conversations",
              "ai_bookings",
              "rate_pct",
              "team_helped",
              "existing",
              "cancelled",
              "truncated",
              "previous",
              "rule"
            ],
            "description": "The AI booking rate BY CONVERSATION (vitrina-app#3711), beside the agenda-based `bookings.share_pct`: of the conversations Vitrina answered on its own, the share where it booked a cita."
          },
          "money": {
            "type": "object",
            "properties": {
              "applies": {
                "type": "boolean",
                "description": "False for a vertical with no priced citas."
              },
              "withheld": {
                "type": "boolean"
              },
              "booked_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Value of the AI-booked citas whose value is known (pipeline, not cash)."
              },
              "booked_priced": {
                "type": "integer"
              },
              "booked_unpriced": {
                "type": "integer",
                "description": "AI-booked citas with no price — «sin dato»."
              },
              "agenda_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "CLP. `null` when unknown or when the caller lacks `clinic_money:read` (`money.withheld`)."
              },
              "clp_share_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "paid_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Cash received in the period by the patients Vitrina booked, from the day it booked them."
              },
              "paid_patients": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "unlinked": {
                "type": "integer",
                "description": "AI-booked citas with no patient to match a payment to."
              },
              "regime": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "mirror",
                  "native"
                ]
              },
              "previous": {
                "type": "object",
                "properties": {
                  "booked_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "CLP. `null` when unknown or when the caller lacks `clinic_money:read` (`money.withheld`)."
                  },
                  "paid_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "CLP. `null` when unknown or when the caller lacks `clinic_money:read` (`money.withheld`)."
                  }
                },
                "required": [
                  "booked_clp",
                  "paid_clp"
                ]
              },
              "rule_booked": {
                "type": "string"
              },
              "rule_paid": {
                "type": "string"
              }
            },
            "required": [
              "applies",
              "withheld",
              "booked_clp",
              "booked_priced",
              "booked_unpriced",
              "agenda_clp",
              "clp_share_pct",
              "paid_clp",
              "paid_patients",
              "unlinked",
              "regime",
              "previous",
              "rule_booked",
              "rule_paid"
            ]
          },
          "out_of_hours": {
            "type": "object",
            "properties": {
              "configured": {
                "type": "boolean",
                "description": "False when the workspace has no business hours; count/pct are then null."
              },
              "count": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "previous_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "heatmap": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "weekday": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 7,
                      "description": "1 = Monday … 7 = Sunday."
                    },
                    "hour": {
                      "type": "integer",
                      "minimum": 0,
                      "maximum": 23
                    },
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "weekday",
                    "hour",
                    "count"
                  ]
                },
                "description": "AI bookings by weekday × hour of the booking, in `timezone`. Empty cells omitted."
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "configured",
              "count",
              "pct",
              "previous_pct",
              "heatmap",
              "rule"
            ]
          },
          "autonomy": {
            "type": "object",
            "properties": {
              "no_human": {
                "type": "integer"
              },
              "pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "measured": {
                "type": "integer"
              },
              "unknown": {
                "type": "integer"
              },
              "previous_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "no_human",
              "pct",
              "measured",
              "unknown",
              "previous_pct",
              "rule"
            ]
          },
          "time_to_book": {
            "type": "object",
            "properties": {
              "median_seconds": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "p95_seconds": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "p95_phrase": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "measured": {
                "type": "integer"
              },
              "unknown": {
                "type": "integer"
              },
              "previous_median_seconds": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "median_seconds",
              "p95_seconds",
              "p95_phrase",
              "measured",
              "unknown",
              "previous_median_seconds",
              "rule"
            ]
          },
          "hours_returned": {
            "type": "object",
            "properties": {
              "minutes_per_booking": {
                "type": "integer"
              },
              "hours": {
                "type": "number"
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "minutes_per_booking",
              "hours",
              "rule"
            ]
          },
          "recovery": {
            "type": "object",
            "properties": {
              "window_days": {
                "type": "integer"
              },
              "known_attendance": {
                "type": "integer"
              },
              "unknown_attendance": {
                "type": "integer"
              },
              "no_show_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "no_show": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "recovered": {
                    "type": "integer"
                  },
                  "recovered_via_vitrina": {
                    "type": "integer"
                  },
                  "recovered_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  },
                  "via_vitrina_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  },
                  "pending": {
                    "type": "integer",
                    "description": "Not recovered yet, still inside the 30 days."
                  },
                  "unlinked": {
                    "type": "integer",
                    "description": "Missed with no identifiable patient."
                  }
                },
                "required": [
                  "count",
                  "recovered",
                  "recovered_via_vitrina",
                  "recovered_pct",
                  "via_vitrina_pct",
                  "pending",
                  "unlinked"
                ]
              },
              "cancelled": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": "integer"
                  },
                  "recovered": {
                    "type": "integer"
                  },
                  "recovered_via_vitrina": {
                    "type": "integer"
                  },
                  "recovered_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  },
                  "via_vitrina_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                  },
                  "pending": {
                    "type": "integer",
                    "description": "Not recovered yet, still inside the 30 days."
                  },
                  "unlinked": {
                    "type": "integer",
                    "description": "Missed with no identifiable patient."
                  }
                },
                "required": [
                  "count",
                  "recovered",
                  "recovered_via_vitrina",
                  "recovered_pct",
                  "via_vitrina_pct",
                  "pending",
                  "unlinked"
                ]
              },
              "rule_no_show": {
                "type": "string"
              },
              "rule_recovered": {
                "type": "string"
              }
            },
            "required": [
              "window_days",
              "known_attendance",
              "unknown_attendance",
              "no_show_pct",
              "no_show",
              "cancelled",
              "rule_no_show",
              "rule_recovered"
            ]
          },
          "confirmation": {
            "type": "object",
            "properties": {
              "applies": {
                "type": "boolean"
              },
              "confirmed_attendance_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "unconfirmed_attendance_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "confirmed_closed": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "unconfirmed_closed": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "applies",
              "confirmed_attendance_pct",
              "unconfirmed_attendance_pct",
              "confirmed_closed",
              "unconfirmed_closed",
              "rule"
            ]
          },
          "ads": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "active",
                  "not_active",
                  "unavailable"
                ]
              },
              "spend_clp": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "booked": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "attended": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "paid": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cost_per_booked": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cost_per_attended": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cost_per_paid": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "state",
              "spend_clp",
              "booked",
              "attended",
              "paid",
              "cost_per_booked",
              "cost_per_attended",
              "cost_per_paid",
              "rule"
            ]
          },
          "reminders": {
            "type": "object",
            "properties": {
              "applies": {
                "type": "boolean"
              },
              "sent": {
                "type": "integer"
              },
              "failed": {
                "type": "integer"
              },
              "failed_pct": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` — never 0 — with no denominator."
              },
              "by_cause": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "cause": {
                      "type": "string",
                      "enum": [
                        "no_patient_link",
                        "no_phone",
                        "no_channel",
                        "opted_out",
                        "template_not_approved",
                        "meta_payment_block",
                        "no_whatsapp",
                        "window_closed",
                        "template_rejected",
                        "send_error"
                      ]
                    },
                    "count": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "cause",
                    "count"
                  ]
                }
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "applies",
              "sent",
              "failed",
              "failed_pct",
              "by_cause",
              "rule"
            ]
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "from",
          "to",
          "timezone",
          "location_id",
          "family",
          "previous_period",
          "bookings",
          "conversation_rate",
          "money",
          "out_of_hours",
          "autonomy",
          "time_to_book",
          "hours_returned",
          "recovery",
          "confirmation",
          "ads",
          "reminders",
          "truncated"
        ]
      },
      "AgendaResultsPeople": {
        "type": "object",
        "properties": {
          "metric": {
            "type": "string",
            "enum": [
              "ai_bookings",
              "out_of_hours",
              "paid_patients"
            ]
          },
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The number on the page, by construction (one read): `bookings.ai`, `out_of_hours.count` or `money.paid_patients`. `null` = not measured (no schedule; money withheld or nobody linked)."
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "patient_link": {
                  "type": "string",
                  "enum": [
                    "linked",
                    "none",
                    "unknown"
                  ]
                },
                "patient": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string"
                    },
                    "rut": {
                      "type": "string"
                    },
                    "phone": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id"
                  ],
                  "description": "The roster patient — same shape and identity gate as `/ads/attributed-sales` (`AdsLinkedPatient`): `name` / `rut` / `phone` need `contacts:read` + `clinic:read` (plus `clinic_patients:read` for an API credential)."
                },
                "contact": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "display_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "key": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "id",
                    "display_name",
                    "key"
                  ]
                },
                "cita": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "display_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "starts_at": {
                      "type": "string",
                      "description": "When the cita happens (ISO)."
                    },
                    "booked_at": {
                      "type": "string",
                      "description": "When the assistant booked it (ISO)."
                    },
                    "status": {
                      "type": "string"
                    },
                    "professional": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ]
                    },
                    "out_of_hours": {
                      "type": [
                        "boolean",
                        "null"
                      ],
                      "description": "Booked while closed; `null` when no business hours are set."
                    }
                  },
                  "required": [
                    "id",
                    "display_id",
                    "starts_at",
                    "booked_at",
                    "status",
                    "professional",
                    "out_of_hours"
                  ],
                  "description": "The cita (`ai_bookings` / `out_of_hours`); for `paid_patients`, their first AI-booked cita of the window."
                },
                "paid": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "total": {
                      "type": "number",
                      "description": "CLP."
                    },
                    "count": {
                      "type": "integer"
                    },
                    "since_day": {
                      "type": "string",
                      "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
                      "description": "The day Vitrina first booked them; payments count from it."
                    },
                    "payments": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "day": {
                            "type": "string",
                            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                          },
                          "amount": {
                            "type": "number"
                          }
                        },
                        "required": [
                          "day",
                          "amount"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total",
                    "count",
                    "since_day",
                    "payments"
                  ],
                  "description": "`paid_patients` only."
                }
              },
              "required": [
                "key",
                "patient_link",
                "patient",
                "contact",
                "cita",
                "paid"
              ]
            },
            "description": "Exactly `count` rows (citas by start; payers by total)."
          },
          "truncated": {
            "type": "boolean",
            "description": "The window read hit its row cap, as the page’s own."
          },
          "withheld": {
            "type": "boolean",
            "description": "`paid_patients` without `clinic_money:read`: no list."
          },
          "people_state": {
            "type": "string",
            "enum": [
              "ready",
              "unavailable"
            ]
          }
        },
        "required": [
          "metric",
          "from",
          "to",
          "location_id",
          "count",
          "people",
          "truncated",
          "withheld",
          "people_state"
        ]
      },
      "ClinicComercialReport": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
          },
          "timezone": {
            "type": "string"
          },
          "location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "professional_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "state": {
            "type": "string",
            "enum": [
              "ready",
              "settling",
              "empty"
            ]
          },
          "state_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "regime": {
            "type": "string",
            "enum": [
              "evidencia",
              "explicita"
            ]
          },
          "regime_label": {
            "type": "string"
          },
          "regime_rule": {
            "type": "string"
          },
          "source": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "mirror",
                  "native"
                ]
              },
              "synced_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            },
            "required": [
              "kind",
              "synced_at"
            ]
          },
          "formulas": {
            "type": "object",
            "properties": {
              "acceptance": {
                "type": "string",
                "enum": [
                  "mentores_2026_09_25"
                ]
              },
              "cierre": {
                "type": "string",
                "enum": [
                  "cierre_2026_09_25"
                ]
              },
              "first_visit": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              },
              "second_visit": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              },
              "botados": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              }
            },
            "required": [
              "acceptance",
              "cierre",
              "first_visit",
              "second_visit",
              "botados"
            ]
          },
          "headline": {
            "type": "object",
            "properties": {
              "collected_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Cash collected in the window. `null` with a professional filter: a payment has no professional."
              },
              "previous_collected_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "delta_pct": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "previous_from": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "previous_to": {
                "type": "string",
                "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "collected_clp",
              "previous_collected_clp",
              "delta_pct",
              "previous_from",
              "previous_to",
              "rule"
            ]
          },
          "acceptance": {
            "type": "object",
            "properties": {
              "presented": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Presupuestos presented (issued), one per lineage. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "accepted": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "rate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
              },
              "same_day": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Accepted the day they were issued. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "later": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "cita_only": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Accepted only through a cita not attended — still ahead, or cancelled / missed after it was booked. The acceptance stays (counted at the moment, not taken back); this is the risk the owner sees. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "pending": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Not accepted yet, window still open. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "presented_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "CLP presented, entry lines excluded. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "accepted_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
              },
              "rate_60d": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
              },
              "formula": {
                "type": "string",
                "enum": [
                  "mentores_2026_09_25"
                ]
              },
              "window_days": {
                "type": "integer"
              },
              "long_window_days": {
                "type": "integer"
              },
              "rule": {
                "type": "string"
              },
              "reference": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "label": {
                    "type": "string",
                    "description": "«Rango de referencia del programa»."
                  },
                  "especialidad": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "implantologia",
                      "ortodoncia",
                      "estetica"
                    ],
                    "description": "The especialidad whose range applies; `null` = the general range."
                  },
                  "band": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "muy_malo",
                      "malo",
                      "regular",
                      "bueno",
                      "excelente"
                    ],
                    "description": "Where the rate falls; `null` when the rate is `null`."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "At `excelente`: «A este nivel conviene revisar tus precios.»"
                  },
                  "muy_malo_upper_pct": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "malo_upper_pct": {
                    "type": "number"
                  },
                  "regular_upper_pct": {
                    "type": "number"
                  },
                  "excelente_lower_pct": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "label",
                  "especialidad",
                  "band",
                  "note",
                  "muy_malo_upper_pct",
                  "malo_upper_pct",
                  "regular_upper_pct",
                  "excelente_lower_pct"
                ],
                "description": "The ODM mentors' reference band for this acceptance rate (#2464): below `muy_malo_upper_pct` muy malo, below `malo_upper_pct` malo, below `regular_upper_pct` regular, above `excelente_lower_pct` excelente, bueno otherwise. A dentist reads their especialidad's range when one exists, else the general one. `null` when no range exists — never an invented band."
              }
            },
            "required": [
              "presented",
              "accepted",
              "rate",
              "same_day",
              "later",
              "cita_only",
              "pending",
              "presented_clp",
              "accepted_clp",
              "rate_30d",
              "rate_60d",
              "formula",
              "window_days",
              "long_window_days",
              "rule",
              "reference"
            ]
          },
          "cierres": {
            "type": "object",
            "properties": {
              "count": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "value_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The accepted value of the presupuestos that closed — never the payments. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "accepted_without_payment": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "formula": {
                "type": "string",
                "enum": [
                  "cierre_2026_09_25"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "count",
              "value_clp",
              "accepted_without_payment",
              "formula",
              "rule"
            ]
          },
          "cohorts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string",
                  "description": "Issue month, `YYYY-MM`."
                },
                "presented": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Presupuestos presented (issued), one per lineage. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "accepted": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "rate": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
                },
                "same_day": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Accepted the day they were issued. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "later": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "cita_only": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Accepted only through a cita not attended — still ahead, or cancelled / missed after it was booked. The acceptance stays (counted at the moment, not taken back); this is the risk the owner sees. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "pending": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Not accepted yet, window still open. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "presented_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "CLP presented, entry lines excluded. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "accepted_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "rate_30d": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
                },
                "rate_60d": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
                },
                "mature": {
                  "type": "boolean",
                  "description": "Every presupuesto of the month is past its window."
                }
              },
              "required": [
                "month",
                "presented",
                "accepted",
                "rate",
                "same_day",
                "later",
                "cita_only",
                "pending",
                "presented_clp",
                "accepted_clp",
                "rate_30d",
                "rate_60d",
                "mature"
              ]
            }
          },
          "by_professional": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "professional_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "professional_name": {
                  "type": "string"
                },
                "presented": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Presupuestos presented (issued), one per lineage. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "accepted": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "rate": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
                },
                "same_day": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Accepted the day they were issued. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "later": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "cita_only": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Accepted only through a cita not attended — still ahead, or cancelled / missed after it was booked. The acceptance stays (counted at the moment, not taken back); this is the risk the owner sees. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "pending": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Not accepted yet, window still open. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "presented_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "CLP presented, entry lines excluded. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "accepted_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "cierres": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "cierres_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "reference": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "label": {
                      "type": "string",
                      "description": "«Rango de referencia del programa»."
                    },
                    "especialidad": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "implantologia",
                        "ortodoncia",
                        "estetica"
                      ],
                      "description": "The especialidad whose range applies; `null` = the general range."
                    },
                    "band": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "muy_malo",
                        "malo",
                        "regular",
                        "bueno",
                        "excelente"
                      ],
                      "description": "Where the rate falls; `null` when the rate is `null`."
                    },
                    "note": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "At `excelente`: «A este nivel conviene revisar tus precios.»"
                    },
                    "muy_malo_upper_pct": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "malo_upper_pct": {
                      "type": "number"
                    },
                    "regular_upper_pct": {
                      "type": "number"
                    },
                    "excelente_lower_pct": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "label",
                    "especialidad",
                    "band",
                    "note",
                    "muy_malo_upper_pct",
                    "malo_upper_pct",
                    "regular_upper_pct",
                    "excelente_lower_pct"
                  ],
                  "description": "The ODM mentors' reference band for this acceptance rate (#2464): below `muy_malo_upper_pct` muy malo, below `malo_upper_pct` malo, below `regular_upper_pct` regular, above `excelente_lower_pct` excelente, bueno otherwise. A dentist reads their especialidad's range when one exists, else the general one. `null` when no range exists — never an invented band."
                }
              },
              "required": [
                "professional_id",
                "professional_name",
                "presented",
                "accepted",
                "rate",
                "same_day",
                "later",
                "cita_only",
                "pending",
                "presented_clp",
                "accepted_clp",
                "cierres",
                "cierres_clp",
                "reference"
              ]
            }
          },
          "unassigned_presented": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
          },
          "unassigned_cierres": {
            "type": [
              "integer",
              "null"
            ],
            "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
          },
          "months": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "month": {
                  "type": "string"
                },
                "accepted_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` for a caller without `clinic_money:read`: month rows then carry only the cash."
                },
                "produced_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` for a caller without `clinic_money:read`: month rows then carry only the cash."
                },
                "collected_clp": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "cierres": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                },
                "cierres_clp": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                }
              },
              "required": [
                "month",
                "accepted_clp",
                "produced_clp",
                "collected_clp",
                "cierres",
                "cierres_clp"
              ]
            }
          },
          "first_visit": {
            "type": "object",
            "properties": {
              "formula": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              },
              "patients": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "New patients whose first-ever attended cita falls in the window. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "paid": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Of them, the ones who paid MORE than the evaluación that day. `null` also when the clinic has no priced evaluación (`reason`). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "rate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
              },
              "evaluation_price_clp": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The clinic's evaluación price the day's payments are compared to (the highest priced evaluación prestación). `null` = none priced."
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Why `rate` is `null` when patients exist (no priced evaluación)."
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "formula",
              "patients",
              "paid",
              "rate",
              "evaluation_price_clp",
              "reason",
              "rule"
            ]
          },
          "second_visit": {
            "type": "object",
            "properties": {
              "formula": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              },
              "eligible": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Accepted presupuestos of the cohort. `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "converted": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Of them, accepted after the issue day («capturado en una visita posterior»). `null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "rate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Percent, one decimal. `null` when there was nothing to divide — never 0 — or, for a caller without `clinic_money:read`, when it rests on fewer than 5 cases."
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "formula",
              "eligible",
              "converted",
              "rate",
              "rule"
            ]
          },
          "botados": {
            "type": "object",
            "properties": {
              "unaccepted": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  },
                  "clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  }
                },
                "required": [
                  "count",
                  "clp"
                ],
                "description": "«Nunca aceptó»: nothing performed, no cita and no pago in the 90 days since issue, no future cita. CLP = the presupuesto."
              },
              "accepted_unscheduled": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  },
                  "clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  }
                },
                "required": [
                  "count",
                  "clp"
                ],
                "description": "Always empty since `mentores_2026_09_29`: an accepted presupuesto has a performed line or a booked cita. Kept so the contract only grew."
              },
              "mid_treatment_no_future_cita": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  },
                  "clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  }
                },
                "required": [
                  "count",
                  "clp"
                ],
                "description": "«Quedó a medias»: something performed, value left, no cita and no pago in the last 90 days, no future cita. CLP = what is left unperformed."
              },
              "by_professional": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "professional_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "professional_name": {
                      "type": "string"
                    },
                    "unaccepted": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                        },
                        "clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                        }
                      },
                      "required": [
                        "count",
                        "clp"
                      ]
                    },
                    "mid_treatment_no_future_cita": {
                      "type": "object",
                      "properties": {
                        "count": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                        },
                        "clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                        }
                      },
                      "required": [
                        "count",
                        "clp"
                      ]
                    }
                  },
                  "required": [
                    "professional_id",
                    "professional_name",
                    "unaccepted",
                    "mid_treatment_no_future_cita"
                  ]
                },
                "description": "Both lists per ISSUING professional, most CLP first. Empty for a caller without `clinic_money:read`."
              },
              "formula": {
                "type": "string",
                "enum": [
                  "propuesta",
                  "mentores_2026_09_29"
                ]
              },
              "basis": {
                "type": "string",
                "enum": [
                  "live_last_6_months",
                  "live_last_12_months"
                ],
                "description": "A live snapshot over the presupuestos issued in the last 12 months (`live_last_6_months` is no longer emitted)."
              },
              "idle_days": {
                "type": "integer",
                "description": "Days without cita or pago that make a presupuesto botado (90)."
              },
              "total": {
                "type": "object",
                "properties": {
                  "count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  },
                  "clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
                  }
                },
                "required": [
                  "count",
                  "clp"
                ]
              },
              "focus": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "unaccepted",
                  "accepted_unscheduled",
                  "mid_treatment_no_future_cita"
                ]
              },
              "rule": {
                "type": "string"
              }
            },
            "required": [
              "unaccepted",
              "accepted_unscheduled",
              "mid_treatment_no_future_cita",
              "by_professional",
              "formula",
              "basis",
              "idle_days",
              "total",
              "focus",
              "rule"
            ]
          },
          "coverage": {
            "type": "object",
            "properties": {
              "plans_without_lines": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "excluded_zero": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              },
              "excluded_entry_only": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "`null` only for a caller without `clinic_money:read`, when it rests on 1–4 cases."
              }
            },
            "required": [
              "plans_without_lines",
              "excluded_zero",
              "excluded_entry_only"
            ]
          },
          "suppression": {
            "type": "object",
            "properties": {
              "min_count": {
                "type": "integer"
              },
              "professionals_withheld": {
                "type": "integer"
              },
              "filters_ignored": {
                "type": "boolean",
                "description": "The Consultor view always reads the last 3 closed calendar months (the report's `from`/`to`) and ignores the requested range and professional / sucursal filters."
              }
            },
            "required": [
              "min_count",
              "professionals_withheld"
            ]
          }
        },
        "required": [
          "from",
          "to",
          "timezone",
          "location_id",
          "professional_id",
          "state",
          "state_reason",
          "regime",
          "regime_label",
          "regime_rule",
          "source",
          "formulas",
          "headline",
          "acceptance",
          "cierres",
          "cohorts",
          "by_professional",
          "unassigned_presented",
          "unassigned_cierres",
          "months",
          "first_visit",
          "second_visit",
          "botados",
          "coverage"
        ]
      },
      "AdsState": {
        "type": "object",
        "properties": {
          "entitlement": {
            "type": "string",
            "enum": [
              "off",
              "pending_on",
              "on",
              "pending_off"
            ]
          },
          "needs_remint": {
            "type": "boolean",
            "description": "True when the live key is the wrong kind for the entitlement: ON with a core key, or OFF with an Ads key."
          },
          "key_kind": {
            "type": "string",
            "enum": [
              "core",
              "ads"
            ]
          },
          "grant_scopes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "entitlement",
          "needs_remint",
          "key_kind",
          "grant_scopes",
          "last_error"
        ]
      },
      "AdsConversionSyncCatalog": {
        "type": "object",
        "properties": {}
      },
      "AdsMetaPixel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_fired_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_unavailable": {
            "type": "boolean"
          }
        },
        "required": [
          "id"
        ]
      },
      "AdsExportDestinations": {
        "type": "object",
        "properties": {
          "privacy_mode": {
            "type": "string"
          },
          "export_meta_enabled": {
            "type": "boolean"
          },
          "export_google_enabled": {
            "type": "boolean"
          },
          "destinations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {}
            }
          }
        },
        "required": [
          "privacy_mode",
          "export_meta_enabled",
          "export_google_enabled",
          "destinations"
        ]
      },
      "AdsPrivacyCheck": {
        "type": "object",
        "properties": {
          "required": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "platform_safe"
            ]
          },
          "actual": {
            "type": "string"
          },
          "applied": {
            "type": "boolean"
          }
        },
        "required": [
          "required",
          "actual",
          "applied"
        ]
      },
      "AdsDestinationResult": {
        "type": "object",
        "properties": {
          "action": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "enabled",
              "unchanged"
            ],
            "description": "`enabled` = a destination already sent to this dataset and was switched back on; `unchanged` = it already sent there, enabled; nothing was written. A dataset another Meta destination of the profile already holds is never written onto a second one (`409`, `destination_conflict`)."
          },
          "lane": {
            "type": "string",
            "enum": [
              "website",
              "whatsapp",
              "other"
            ],
            "description": "The lane of the destination that now carries the dataset: `website` = the pixel, every outcome without a click-to-WhatsApp click id; `whatsapp` = the WhatsApp dataset, only outcomes with one."
          },
          "destination": {
            "type": "object",
            "properties": {}
          },
          "privacy": {
            "$ref": "#/components/schemas/AdsPrivacyCheck"
          }
        },
        "required": [
          "action",
          "lane",
          "destination",
          "privacy"
        ]
      },
      "AdsRuleSyncResult": {
        "type": "object",
        "properties": {
          "vertical": {
            "type": [
              "string",
              "null"
            ]
          },
          "rules": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "stage": {
                  "type": "string",
                  "enum": [
                    "lead_created",
                    "lead_qualified",
                    "appointment_booked",
                    "checkout_started",
                    "deposit_paid",
                    "appointment_attended",
                    "quote_presented",
                    "closed_won",
                    "first_payment",
                    "returning_first_payment",
                    "payment_received",
                    "seller_qualified",
                    "seller_visit_booked",
                    "seller_deal_closed"
                  ]
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "created",
                    "updated",
                    "unchanged",
                    "skipped"
                  ],
                  "description": "`unchanged` = the rule already held this body; nothing was written."
                },
                "rule_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "meta_enabled": {
                  "type": "boolean"
                },
                "meta_event_name": {
                  "type": "string"
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "definition_missing"
                  ]
                }
              },
              "required": [
                "stage",
                "action",
                "rule_id",
                "meta_enabled",
                "meta_event_name"
              ]
            }
          }
        },
        "required": [
          "vertical",
          "rules"
        ]
      },
      "AdsConversionSyncSetupResult": {
        "type": "object",
        "properties": {
          "destination": {
            "$ref": "#/components/schemas/AdsDestinationResult"
          },
          "rules": {
            "$ref": "#/components/schemas/AdsRuleSyncResult"
          }
        },
        "required": [
          "destination",
          "rules"
        ]
      },
      "AdsConversionSyncWiring": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {}
            },
            "description": "The conversion provider's per-rule × destination verdicts and remediation, verbatim: `{rule_id, destination_id, verdict, event_name, checks, remediation[], checked_at, stale}`, `verdict` ∈ `wired | sent_not_used | below_threshold | custom_conversion_broken | connection_broken | check_failed`. `checks` also carries Meta ids (`pixel.pixelId`, `optimizationGoal.usedByActiveAdSets[].adSetId` / `campaignId`), elided from the example."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "stale": {
            "type": "boolean"
          },
          "ttl_ms": {
            "type": "number"
          },
          "privacy": {
            "$ref": "#/components/schemas/AdsPrivacyCheck"
          }
        },
        "required": [
          "results",
          "checked_at",
          "stale",
          "ttl_ms"
        ]
      },
      "AdsSendStatus": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "not_started",
              "on",
              "partial",
              "off",
              "blocked",
              "measure_only"
            ],
            "description": "`blocked` = only Meta or the owner outside Vitrina can fix it (`blocked_reason`). `off` = set up once and every stage stopped. `measure_only` = the owner chose «Solo medir, no enviar a Meta» (reversible with `send_setup`)."
          },
          "blocked_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "no_dataset",
              "datasets_unavailable"
            ]
          },
          "wizard_done": {
            "type": "boolean",
            "description": "The activation wizard’s «Enviar a Meta» step is resolved: `state` is `on`, `partial`, `blocked` or `measure_only`."
          },
          "meta_connection": {
            "type": "string",
            "enum": [
              "connected",
              "not_connected",
              "reconnect_required",
              "unknown"
            ]
          },
          "dataset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": "string",
                "description": "Meta’s dataset (pixel) id."
              },
              "id_tail": {
                "type": "string",
                "description": "Its last 4 digits («…4821»)."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "last_fired_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "is_unavailable": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "`null` = Meta gave no verdict (unknown, not healthy)."
              }
            },
            "required": [
              "external_id",
              "id_tail",
              "name",
              "last_fired_at",
              "is_unavailable"
            ],
            "description": "The dataset Meta receives the events in today."
          },
          "datasets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "external_id": {
                  "type": "string",
                  "description": "Meta’s dataset (pixel) id."
                },
                "id_tail": {
                  "type": "string",
                  "description": "Its last 4 digits («…4821»)."
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "last_fired_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "is_unavailable": {
                  "type": [
                    "boolean",
                    "null"
                  ],
                  "description": "`null` = Meta gave no verdict (unknown, not healthy)."
                },
                "recommended": {
                  "type": "boolean"
                },
                "recommended_reason": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "used_by_ad_sets",
                    "most_recent",
                    "only_one"
                  ],
                  "description": "`used_by_ad_sets` = active ad sets already optimise on it (keep it: Meta keeps learning where it learns)."
                },
                "selected": {
                  "type": "boolean"
                }
              },
              "required": [
                "external_id",
                "id_tail",
                "name",
                "last_fired_at",
                "is_unavailable",
                "recommended",
                "recommended_reason",
                "selected"
              ]
            },
            "description": "The datasets of the connected ad account."
          },
          "whatsapp_dataset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": "string"
              },
              "id_tail": {
                "type": "string"
              },
              "enabled": {
                "type": "boolean"
              }
            },
            "required": [
              "external_id",
              "id_tail",
              "enabled"
            ],
            "description": "The WhatsApp lane’s own dataset when the profile has one beside `dataset`: outcomes that carry a click-to-WhatsApp click id reach Meta there (as `business_messaging`), everything else in `dataset`. `null` = one dataset carries everything. «Enviar a Meta» never moves either lane onto the other’s dataset."
          },
          "stages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "stage": {
                  "type": "string",
                  "enum": [
                    "lead_created",
                    "lead_qualified",
                    "appointment_booked",
                    "checkout_started",
                    "deposit_paid",
                    "appointment_attended",
                    "quote_presented",
                    "closed_won",
                    "first_payment",
                    "returning_first_payment",
                    "payment_received",
                    "seller_qualified",
                    "seller_visit_booked",
                    "seller_deal_closed"
                  ]
                },
                "label": {
                  "type": "string",
                  "description": "The stage in the workspace’s words."
                },
                "meta_event": {
                  "type": "string",
                  "description": "What Meta receives."
                },
                "meta_event_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Every event name Meta may receive for the stage, the headline first (`meta_event`), then the WhatsApp lane's name when it differs: a clinic's attended stage reads `['appointment_attended', 'QualifiedLead']` when a WhatsApp dataset is connected."
                },
                "meta_event_messaging": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The name the stage reaches Meta under on the WhatsApp dataset (one of the 14 names that dataset accepts, e.g. `QualifiedLead`). `null`: no WhatsApp dataset is connected, or the stage is skipped on that lane. The website pixel keeps `meta_event`."
                },
                "meta_event_kind": {
                  "type": "string",
                  "enum": [
                    "standard",
                    "custom"
                  ]
                },
                "meta_event_neutral": {
                  "type": "boolean",
                  "description": "A custom stage that reaches Meta under Vitrina’s opaque neutral name (`vitrina_step_3` attended, `vitrina_step_4` quote presented, `vitrina_step_5` accepted presupuesto, `vitrina_step_7` a returning customer’s first payment), which names no stage. `false` for a standard event, and for a custom stage that keeps its literal name (`appointment_attended`, …) because Meta already holds history under it."
                },
                "value": {
                  "type": "string",
                  "enum": [
                    "accepted_total",
                    "payment",
                    "none"
                  ],
                  "description": "The value Meta receives with the event."
                },
                "enabled": {
                  "type": "boolean",
                  "description": "It reaches Meta today."
                },
                "switchable": {
                  "type": "boolean"
                },
                "lock": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "installments"
                  ],
                  "description": "`installments`: a clinic’s later payments never reach Meta, so one sale is never counted twice. No switch."
                },
                "awaiting_switch": {
                  "type": "boolean",
                  "description": "A clinic’s first payment before the switch: Meta does not receive it yet (its Purchase is still the accepted presupuesto). `send_stage:first_payment:on` makes the switch, with consent."
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "available",
                    "no_payments_source",
                    "definition_missing"
                  ],
                  "description": "`definition_missing` heals on its own (no user step); `no_payments_source` = no payments recorded to send yet."
                },
                "ready": {
                  "type": "boolean",
                  "description": "If the stage happened now, it would reach Meta: switched on and its source has what it needs."
                },
                "ready_reason": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "installments",
                    "awaiting_switch",
                    "definition_missing",
                    "no_payments_yet",
                    "off"
                  ],
                  "description": "Why it would not reach Meta now; `null` when `ready`. Precedence: `installments` > `awaiting_switch` > `definition_missing` > `no_payments_yet` > `off`."
                },
                "count_28d": {
                  "type": "integer",
                  "description": "Outcomes of the stage that happened in Vitrina over the page’s window — the last 30 days (`window_days`; the field name predates it), the same days the dataset preview counts — sent or not. `first_payment`: `first_payment_candidates.count_28d` (first payments of accepted plans, or — a clinic without plans — persons’ first payments), before the switch too."
                },
                "weekly_28d": {
                  "type": "number",
                  "description": "«Por semana», one decimal: the mean of the last 4 COMPLETE weeks (Monday–Sunday, Santiago) in which the stage was being measured — a week before its first recorded outcome is left out, never counted as zero. With no complete measured week yet: the days measured so far × 7 (divisor floor of 7 days). The dataset preview’s `weekly` for the same stage is the same figure."
                },
                "measured_weeks": {
                  "type": "integer",
                  "description": "Complete weeks `weekly_28d` averages, 0–4. 4 = «promedio de las últimas 4 semanas»; fewer = «promedio de N semanas»; 0 = estimated over `measured_days`."
                },
                "measured_days": {
                  "type": "integer",
                  "description": "Days the stage has been measured, capped at the 30-day window; 0 = nothing recorded yet."
                },
                "events_7d": {
                  "type": "integer",
                  "description": "Outcomes of the stage that happened in the last 7 days (Vitrina’s outbox)."
                },
                "with_ad_click_7d": {
                  "type": "integer",
                  "description": "Of `events_7d`, those carrying the ad’s click id (`ctwa_clid`): the ones Meta can credit to a click-to-WhatsApp ad. The rest reach the dataset as a conversion without ad (a truthful non-messaging action_source)."
                },
                "events_30d": {
                  "type": "integer",
                  "description": "Outcomes of the stage that happened in the last 30 days (`window_days`) in Vitrina’s outbox — what travelled (or waits) toward Meta. For a clinic’s `first_payment` this is NOT `count_28d` (the claims, which include first payments too old for Meta)."
                },
                "with_ad_click_30d": {
                  "type": "integer",
                  "description": "Of `events_30d`, those carrying the ad’s click id (`ctwa_clid`): the WhatsApp dataset’s lane, the ones Meta credits to a click-to-WhatsApp ad."
                },
                "without_ad_click_7d": {
                  "type": "integer",
                  "description": "`events_7d - with_ad_click_7d`: sent without the click, through the website pixel (or not sent without one)."
                },
                "without_ad_click_30d": {
                  "type": "integer",
                  "description": "`events_30d - with_ad_click_30d`."
                },
                "window_days": {
                  "type": "number",
                  "description": "The window `count_28d` covers: 30 days, the page’s «últimos 30 días»."
                },
                "verdict": {
                  "type": "string",
                  "enum": [
                    "wired",
                    "sent_not_used",
                    "below_threshold",
                    "custom_conversion_broken",
                    "missing_custom_conversion",
                    "connection_broken",
                    "check_failed",
                    "not_checked"
                  ]
                },
                "active_ad_sets": {
                  "type": "integer"
                },
                "last_sent_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "sent_7d": {
                  "type": "integer"
                },
                "meta_volume_7d": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "What Meta counted for this event on the dataset over 7 days."
                },
                "series_7d": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string",
                        "description": "America/Santiago day, `YYYY-MM-DD`."
                      },
                      "count": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "date",
                      "count"
                    ]
                  }
                },
                "weekly_learning_target": {
                  "type": "integer"
                },
                "changed_by": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Who turned it off — a display name, only for a caller holding `memberships:read`."
                },
                "changed_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "value_capped_7d": {
                  "type": "integer",
                  "description": "Events of the last 7 days whose value Meta received capped at the workspace’s 95th percentile of accepted plans (≥ 20 plans). The full value stays in Vitrina’s reporting."
                },
                "value_cap_clp": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The cap applied, CLP."
                },
                "too_old_28d": {
                  "type": "integer",
                  "description": "Of the outcomes that happened in the window (`window_days`), those handed over — or still waiting — more than 7 days after they happened: Meta refuses events older than 7 days, so these never reach it. Never retried. A history import of older outcomes is not counted."
                },
                "purchases_reversed_28d": {
                  "type": "integer",
                  "description": "`first_payment` only: Purchases of the last 28 days whose payment was later reversed. Meta keeps them (it has no refund event)."
                },
                "sample_arrival_ms": {
                  "type": "integer",
                  "description": "Sample mode only: when the scripted arrival plays."
                }
              },
              "required": [
                "stage",
                "label",
                "meta_event",
                "meta_event_names",
                "meta_event_messaging",
                "meta_event_kind",
                "meta_event_neutral",
                "value",
                "enabled",
                "switchable",
                "lock",
                "awaiting_switch",
                "source",
                "ready",
                "ready_reason",
                "count_28d",
                "weekly_28d",
                "measured_weeks",
                "measured_days",
                "events_7d",
                "with_ad_click_7d",
                "events_30d",
                "with_ad_click_30d",
                "without_ad_click_7d",
                "without_ad_click_30d",
                "window_days",
                "verdict",
                "active_ad_sets",
                "last_sent_at",
                "sent_7d",
                "meta_volume_7d",
                "series_7d",
                "weekly_learning_target",
                "changed_by",
                "changed_at",
                "value_capped_7d",
                "value_cap_clp",
                "too_old_28d",
                "purchases_reversed_28d"
              ]
            }
          },
          "enabled_count": {
            "type": "integer",
            "description": "Counted stages that are `ready` — «{enabled_count} de {stage_count} etapas llegan a Meta». A switched-on stage whose source has no data yet is in `waiting_count` instead."
          },
          "waiting_count": {
            "type": "integer",
            "description": "Counted stages switched on but not `ready` yet («1 espera pagos»). `enabled_count + waiting_count` = the switched-on counted stages."
          },
          "stage_count": {
            "type": "integer",
            "description": "«{enabled_count} de {stage_count} etapas»; locked stages and the first payment before the switch excluded."
          },
          "first_payment_candidates": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "plan",
                  "person"
                ],
                "description": "What the Purchase is the first payment OF. `plan`: the first payment of each accepted plan (a clinic that keeps presupuestos or a mirrored clinic system with plans). `person`: a clinic that keeps NO plans (no live presupuesto, no mirrored plan — read from its data, never a setting) — each person’s first payment > 0, once ever per person, valued at the amount paid (capped at the 95th percentile of the clinic’s payments of the last year once there are ≥ 20); later payments never reach Meta."
              },
              "count_28d": {
                "type": "integer",
                "description": "First payments over the window (the last 30 days; the field name predates it), sent or not — what reaches Meta as Purchase once switched. `basis: plan`: first payments of accepted plans, in Vitrina’s payment ledger and in the clinic system’s mirror, one per sale. `basis: person`: persons whose first payment ever fell in the window."
              },
              "weekly_28d": {
                "type": "number",
                "description": "«Por semana» of the new-patient first payments, one decimal — the mean of the last 4 complete measured weeks (see the stage’s `weekly_28d`), measured from the first claim."
              },
              "measured_weeks": {
                "type": "integer",
                "description": "Complete weeks `weekly_28d` averages, 0–4."
              },
              "measured_days": {
                "type": "integer",
                "description": "Days measured, capped at 30."
              },
              "mirror_count_28d": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Of `count_28d`, the first payments claimed from the clinic system’s mirror (they reach Meta like the ledger’s). `null` without a mirror opt-in."
              },
              "mirror_weekly_28d": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "basis",
              "count_28d",
              "weekly_28d",
              "measured_weeks",
              "measured_days",
              "mirror_count_28d",
              "mirror_weekly_28d"
            ],
            "description": "Clinics: the Purchase volume of the first payment, counted before the switch too. `null` outside clinics or when it could not be read."
          },
          "first_payment_click_match": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "stage": {
                "type": "string",
                "enum": [
                  "first_payment"
                ]
              },
              "required_7d": {
                "type": "integer",
                "description": "Meta’s minimum: about 50 optimisation events per 7 days to leave the learning phase (Meta Business Help Center, «About the learning phase»)."
              },
              "with_ad_click_7d": {
                "type": "integer",
                "description": "First payments of the last 7 days sent WITH the ad’s click id (`ctwa_clid`): the only ones the WhatsApp dataset can optimise on."
              },
              "remaining_7d": {
                "type": "integer",
                "description": "`max(0, required_7d - with_ad_click_7d)`."
              },
              "reached": {
                "type": "boolean"
              },
              "without_ad_click_7d": {
                "type": "integer",
                "description": "First payments of the last 7 days sent without the click (website pixel lane): they never count toward `required_7d`."
              },
              "with_ad_click_30d": {
                "type": "integer"
              },
              "without_ad_click_30d": {
                "type": "integer"
              },
              "held_back_30d": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "too_old": {
                    "type": "integer",
                    "description": "New patients’ first payments of the last 30 days already older than Meta’s 7-day event window when Vitrina claimed them (the clinic system’s history, a late entry): never sent."
                  },
                  "no_contact": {
                    "type": "integer",
                    "description": "First payments with no patient contact to send them under (retried while fresh)."
                  }
                },
                "required": [
                  "too_old",
                  "no_contact"
                ],
                "description": "Why first payments did not reach Meta at all. `null` for a clinic without plans or when it could not be read."
              }
            },
            "required": [
              "stage",
              "required_7d",
              "with_ad_click_7d",
              "remaining_7d",
              "reached",
              "without_ad_click_7d",
              "with_ad_click_30d",
              "without_ad_click_30d",
              "held_back_30d"
            ],
            "description": "Clinics: click-matched first payments toward Meta’s 50-per-week minimum («44 de 50»). `null` outside clinics."
          },
          "restriction": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "none",
                  "limited",
                  "blocked",
                  "unknown"
                ]
              },
              "since": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "since"
            ],
            "description": "Health-dataset restriction by Meta (clinics): `blocked` = Meta counted none of the Lead / Schedule / Purchase sent on ≥ 3 of the last 7 days. `unknown` when it could not be read; `none` outside clinics."
          },
          "protected_dataset": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "provisioned",
                  "missing",
                  "preparing"
                ]
              },
              "ad_sets_to_move": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "Active ad sets still optimising on the restricted dataset (null = not read)."
              }
            },
            "required": [
              "state",
              "ad_sets_to_move"
            ],
            "description": "Clinics: the protected replacement dataset Vitrina prepares on its own when Meta restricts the dataset. Sending never waits for it; moving the ad sets is a separate, consented action. `null` outside clinics."
          },
          "purchase_stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "closed_won",
              "first_payment"
            ],
            "description": "Clinics: which stage carries the Meta Purchase today — the accepted presupuesto until the owner’s consented switch to the first payment. No rules sync ever moves it. `null` outside clinics."
          },
          "purchase_switch_available": {
            "type": "boolean",
            "description": "The switch of the Purchase to the first payment can be made now."
          },
          "recommended_optimization_stage": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "stage": {
                "type": "string",
                "enum": [
                  "lead_created",
                  "lead_qualified",
                  "appointment_booked",
                  "checkout_started",
                  "deposit_paid",
                  "appointment_attended",
                  "quote_presented",
                  "closed_won",
                  "first_payment",
                  "returning_first_payment",
                  "payment_received",
                  "seller_qualified",
                  "seller_visit_booked",
                  "seller_deal_closed"
                ]
              },
              "reason": {
                "type": "string",
                "enum": [
                  "rate_band",
                  "default"
                ]
              },
              "rate": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "stage",
              "reason",
              "rate"
            ],
            "description": "Advice only — Vitrina never changes an ad set’s goal: the earliest stage reached by 1–40 % of new contacts over 28 days, once there are ≥ 50 (what Meta learns best from with this volume); `default` = not enough data yet, the usual answer for the business type. Never a standard event on a health-restricted dataset."
          },
          "grant": {
            "type": "string",
            "enum": [
              "full",
              "read_only",
              "unknown"
            ],
            "description": "`read_only`: the Meta connection cannot manage ads — sending works, fixes that write in Meta do not."
          },
          "protection_required": {
            "type": "boolean",
            "description": "Health data protection applies (clinics)."
          },
          "protected": {
            "type": "boolean"
          },
          "consent": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version": {
                "type": "string"
              },
              "current": {
                "type": "boolean"
              },
              "accepted_at": {
                "type": "string"
              }
            },
            "required": [
              "version",
              "current",
              "accepted_at"
            ],
            "description": "The owner’s last acceptance of the send (the `send_setup` consent)."
          },
          "meta_links": {
            "type": "object",
            "properties": {
              "create_dataset": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "events_manager": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "test_events": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ad_sets": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "create_dataset",
              "events_manager",
              "test_events",
              "ad_sets"
            ],
            "description": "Meta links built on the server from Meta ids. Open in a new tab; they change nothing by themselves."
          },
          "checked_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "stale": {
            "type": "boolean"
          },
          "sample": {
            "type": "boolean"
          },
          "ad_set_goals": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "conversations": {
                "type": "integer",
                "description": "Active click-to-WhatsApp ad sets that optimise for CONVERSATIONS (the engine’s nightly Meta sync)."
              },
              "on_sent_events": {
                "type": "integer",
                "description": "Distinct active ad sets that optimise on an event Vitrina sends (the wiring verdicts)."
              }
            },
            "required": [
              "conversations",
              "on_sent_events"
            ],
            "description": "What the ads optimise for today. `conversations > 0` with `on_sent_events = 0`: the events reach Meta but no ad set learns from them yet (a new ad set optimised for one of them is how to start). `null` = not read."
          }
        },
        "required": [
          "state",
          "blocked_reason",
          "wizard_done",
          "meta_connection",
          "dataset",
          "datasets",
          "stages",
          "enabled_count",
          "waiting_count",
          "stage_count",
          "first_payment_candidates",
          "first_payment_click_match",
          "restriction",
          "protected_dataset",
          "purchase_stage",
          "purchase_switch_available",
          "recommended_optimization_stage",
          "grant",
          "protection_required",
          "protected",
          "consent",
          "meta_links",
          "checked_at",
          "stale",
          "sample",
          "ad_set_goals"
        ]
      },
      "AdsSendPreviewEvent": {
        "type": "object",
        "properties": {
          "stage": {
            "type": "string",
            "enum": [
              "lead_created",
              "lead_qualified",
              "appointment_booked",
              "checkout_started",
              "deposit_paid",
              "appointment_attended",
              "quote_presented",
              "closed_won",
              "first_payment",
              "returning_first_payment",
              "payment_received",
              "seller_qualified",
              "seller_visit_booked",
              "seller_deal_closed"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "own",
              "example"
            ],
            "description": "`example` when the workspace has no event of this stage yet."
          },
          "vitrina": {
            "type": "object",
            "properties": {
              "stage_label": {
                "type": "string"
              },
              "occurred_at": {
                "type": "string"
              },
              "value_clp": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The workspace’s full value, CLP (before Meta’s cap)."
              },
              "contact_tail": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Last 4 characters of the contact’s opaque reference; never a name or a contact detail."
              }
            },
            "required": [
              "stage_label",
              "occurred_at",
              "value_clp",
              "contact_tail"
            ]
          },
          "meta": {
            "type": "object",
            "properties": {
              "event": {
                "type": "string"
              },
              "value_clp": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The value Meta receives, CLP; `null` when Meta gets no value."
              },
              "capped": {
                "type": "boolean",
                "description": "Meta receives the workspace’s 95th-percentile cap instead of the full value (`vitrina.value_clp`)."
              },
              "value_cap_clp": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The cap applied, CLP, when `capped`."
              },
              "reaches_meta": {
                "type": "boolean"
              },
              "not_sent_reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "marketing_opt_out",
                  "stage_off",
                  "stage_locked"
                ]
              },
              "fields": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "enum": [
                        "event",
                        "time",
                        "value",
                        "action_source",
                        "email",
                        "phone",
                        "contact_ref",
                        "national_id",
                        "ad_click"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "sent",
                        "hashed",
                        "withheld",
                        "absent"
                      ],
                      "description": "`hashed`: it crosses only as a hash Meta can match, not read. Identifiers never carry a value."
                    },
                    "display": {
                      "type": "string",
                      "description": "Non-identifying fields only (event, time, value, where it happened)."
                    },
                    "display_channel": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "whatsapp",
                        "instagram",
                        "messenger"
                      ],
                      "description": "`action_source` only: the conversation’s channel when the event was born in a messaging thread; `null` when not, or unknown."
                    }
                  },
                  "required": [
                    "key",
                    "label",
                    "state"
                  ]
                }
              }
            },
            "required": [
              "event",
              "value_clp",
              "capped",
              "value_cap_clp",
              "reaches_meta",
              "not_sent_reason",
              "fields"
            ]
          },
          "never_sent": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What never leaves Vitrina, as keys (`national_id`, `clinical_data`, `notes`, …)."
          }
        },
        "required": [
          "stage",
          "source",
          "vitrina",
          "meta",
          "never_sent"
        ]
      },
      "AdsSendTestResult": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "received",
              "rejected",
              "blocked"
            ],
            "description": "`rejected` = Meta refused it; `blocked` = Vitrina’s own privacy or legal gate held the payload back before Meta (an internal block, never Meta’s answer; `blocked_reason` says which)."
          },
          "blocked_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "`blocked` only: the internal gate’s reason code (e.g. `dpa_not_accepted`, `baa_not_approved`, `privacy_gate`). `null` otherwise."
          },
          "test_event_code": {
            "type": "string"
          },
          "event_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Search for it in Events Manager → Test Events."
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "lead_created",
              "lead_qualified",
              "appointment_booked",
              "checkout_started",
              "deposit_paid",
              "appointment_attended",
              "quote_presented",
              "closed_won",
              "first_payment",
              "returning_first_payment",
              "payment_received",
              "seller_qualified",
              "seller_visit_booked",
              "seller_deal_closed"
            ]
          },
          "meta_event": {
            "type": [
              "string",
              "null"
            ]
          },
          "quality_warning": {
            "type": "boolean"
          },
          "test_events_link": {
            "type": [
              "string",
              "null"
            ]
          },
          "simulated": {
            "type": "boolean"
          }
        },
        "required": [
          "result",
          "test_event_code",
          "event_id",
          "stage",
          "meta_event",
          "quality_warning",
          "test_events_link",
          "simulated"
        ]
      },
      "AdsMessagingEventNames": {
        "type": "object",
        "properties": {
          "overrides": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "The stored choices, `{ stage: name | null }`."
          },
          "accepted_events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "stage": {
                  "type": "string",
                  "enum": [
                    "lead_created",
                    "lead_qualified",
                    "appointment_booked",
                    "checkout_started",
                    "deposit_paid",
                    "appointment_attended",
                    "quote_presented",
                    "closed_won",
                    "first_payment",
                    "returning_first_payment",
                    "payment_received",
                    "seller_qualified",
                    "seller_visit_booked",
                    "seller_deal_closed"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "website_event": {
                  "type": "string",
                  "description": "The website pixel lane’s name (not configurable here)."
                },
                "messaging_default": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The matrix’s own WhatsApp-lane name; `null` = not sent there."
                },
                "messaging_event": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The WhatsApp-lane name in force: the workspace’s choice, else the default; `null` = not sent there."
                },
                "overridden": {
                  "type": "boolean"
                }
              },
              "required": [
                "stage",
                "label",
                "website_event",
                "messaging_default",
                "messaging_event",
                "overridden"
              ]
            }
          }
        },
        "required": [
          "overrides",
          "accepted_events",
          "stages"
        ]
      },
      "AdsMessagingEventNamesPatchResult": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdsMessagingEventNames"
          },
          {
            "type": "object",
            "properties": {
              "sync": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "string",
                    "enum": [
                      "converged",
                      "pending"
                    ],
                    "description": "`converged`: the engine’s live rules now hold the names in force. `pending`: stored, the engine write failed and the daily sweep retries it."
                  },
                  "updated": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "lead_created",
                        "lead_qualified",
                        "appointment_booked",
                        "checkout_started",
                        "deposit_paid",
                        "appointment_attended",
                        "quote_presented",
                        "closed_won",
                        "first_payment",
                        "returning_first_payment",
                        "payment_received",
                        "seller_qualified",
                        "seller_visit_booked",
                        "seller_deal_closed"
                      ]
                    }
                  }
                },
                "required": [
                  "state",
                  "updated"
                ]
              }
            },
            "required": [
              "sync"
            ]
          }
        ]
      },
      "AdsMessagingEventNamesPatch": {
        "type": "object",
        "properties": {
          "overrides": {
            "type": "object",
            "properties": {
              "lead_created": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "lead_qualified": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "appointment_booked": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "checkout_started": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "deposit_paid": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "appointment_attended": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "quote_presented": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "closed_won": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "first_payment": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "returning_first_payment": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "payment_received": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "seller_qualified": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "seller_visit_booked": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              },
              "seller_deal_closed": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "Purchase",
                  "LeadSubmitted",
                  "InitiateCheckout",
                  "AddToCart",
                  "ViewContent",
                  "OrderCreated",
                  "OrderShipped",
                  "OrderDelivered",
                  "OrderCanceled",
                  "OrderReturned",
                  "CartAbandoned",
                  "QualifiedLead",
                  "RatingProvided",
                  "ReviewProvided"
                ]
              }
            },
            "description": "Per stage, the name the WhatsApp dataset receives: one of `accepted_events`, or `null` = the stage is not sent on the WhatsApp lane. Stages left out keep what they have."
          },
          "reset": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "lead_created",
                "lead_qualified",
                "appointment_booked",
                "checkout_started",
                "deposit_paid",
                "appointment_attended",
                "quote_presented",
                "closed_won",
                "first_payment",
                "returning_first_payment",
                "payment_received",
                "seller_qualified",
                "seller_visit_booked",
                "seller_deal_closed"
              ]
            },
            "maxItems": 14,
            "description": "Stages whose choice is dropped: they go back to the matrix's name (`messaging_default`). Applied before `overrides`."
          }
        },
        "additionalProperties": false
      },
      "AdsMetaDatasetStatus": {
        "type": "object",
        "properties": {
          "prerequisites": {
            "type": "object",
            "properties": {
              "meta_ads_connected": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "whatsapp_connected": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "instagram_connected": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "`null` while the measurement engine does not report it."
              }
            },
            "required": [
              "meta_ads_connected",
              "whatsapp_connected",
              "instagram_connected"
            ]
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "website",
                  "instagram"
                ]
              },
              "state": {
                "type": "string",
                "enum": [
                  "coming_soon",
                  "unknown",
                  "prerequisites_missing",
                  "ready",
                  "created",
                  "sending",
                  "off",
                  "error"
                ],
                "description": "`coming_soon` = the measurement engine cannot do this step yet («Pronto»); `unknown` = the engine could not be read now; `prerequisites_missing` = connect Meta Ads (and WhatsApp for the WhatsApp step, Instagram for the Instagram step) first; `ready` = the press would act; `created` = wired but no enabled Meta rule yet, so nothing flows; `sending` = the engine confirms an enabled destination AND an enabled Meta rule; `off` = turned off, receives nothing; `error` = see `reason_code` / `message`."
              },
              "reason_code": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "entitlement_required",
                  "duplicate_kind",
                  "access_missing",
                  "dataset_unreadable",
                  "already_exists",
                  "no_pixel",
                  "pixels_unavailable",
                  "pixel_already_attached",
                  "instagram_facebook_login_required",
                  "meta_refused",
                  "meta_unavailable",
                  "engine_error"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What to do, in Spanish, when `reason_code` is set."
              },
              "dataset": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Meta’s dataset / pixel id."
                  },
                  "id_tail": {
                    "type": "string"
                  },
                  "export_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only what happens from this instant on is sent; never history."
                  },
                  "destination_enabled": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "access_granted": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "shared": {
                    "type": "boolean",
                    "description": "Another workspace already sends to this ad account or dataset."
                  }
                },
                "required": [
                  "external_id",
                  "id_tail",
                  "export_from",
                  "destination_enabled",
                  "access_granted",
                  "shared"
                ]
              },
              "can_create": {
                "type": "boolean"
              },
              "can_turn_off": {
                "type": "boolean"
              },
              "notice": {
                "type": "object",
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "points": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Read before this step’s confirmation, verbatim (Spanish)."
                  },
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 of the points; sent back with the press."
                  }
                },
                "required": [
                  "version",
                  "points",
                  "hash"
                ]
              }
            },
            "required": [
              "kind",
              "state",
              "reason_code",
              "message",
              "dataset",
              "can_create",
              "can_turn_off",
              "notice"
            ],
            "description": "Step 1 — the dataset linked to the WhatsApp Business Account: receives the outcomes carrying the click-to-WhatsApp ad click, as `business_messaging`."
          },
          "website": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "website",
                  "instagram"
                ]
              },
              "state": {
                "type": "string",
                "enum": [
                  "coming_soon",
                  "unknown",
                  "prerequisites_missing",
                  "ready",
                  "created",
                  "sending",
                  "off",
                  "error"
                ],
                "description": "`coming_soon` = the measurement engine cannot do this step yet («Pronto»); `unknown` = the engine could not be read now; `prerequisites_missing` = connect Meta Ads (and WhatsApp for the WhatsApp step, Instagram for the Instagram step) first; `ready` = the press would act; `created` = wired but no enabled Meta rule yet, so nothing flows; `sending` = the engine confirms an enabled destination AND an enabled Meta rule; `off` = turned off, receives nothing; `error` = see `reason_code` / `message`."
              },
              "reason_code": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "entitlement_required",
                  "duplicate_kind",
                  "access_missing",
                  "dataset_unreadable",
                  "already_exists",
                  "no_pixel",
                  "pixels_unavailable",
                  "pixel_already_attached",
                  "instagram_facebook_login_required",
                  "meta_refused",
                  "meta_unavailable",
                  "engine_error"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What to do, in Spanish, when `reason_code` is set."
              },
              "dataset": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Meta’s dataset / pixel id."
                  },
                  "id_tail": {
                    "type": "string"
                  },
                  "export_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only what happens from this instant on is sent; never history."
                  },
                  "destination_enabled": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "access_granted": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "shared": {
                    "type": "boolean",
                    "description": "Another workspace already sends to this ad account or dataset."
                  }
                },
                "required": [
                  "external_id",
                  "id_tail",
                  "export_from",
                  "destination_enabled",
                  "access_granted",
                  "shared"
                ]
              },
              "can_create": {
                "type": "boolean"
              },
              "can_turn_off": {
                "type": "boolean"
              },
              "notice": {
                "type": "object",
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "points": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Read before this step’s confirmation, verbatim (Spanish)."
                  },
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 of the points; sent back with the press."
                  }
                },
                "required": [
                  "version",
                  "points",
                  "hash"
                ]
              },
              "candidates": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "external_id": {
                      "type": "string",
                      "description": "Meta’s pixel id."
                    },
                    "id_tail": {
                      "type": "string"
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "last_fired_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "recommended": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "external_id",
                    "id_tail",
                    "name",
                    "last_fired_at",
                    "recommended"
                  ]
                },
                "description": "The ad account’s pixels to choose from (only while the step is `ready` / `off`)."
              },
              "can_create_pixel": {
                "type": "boolean",
                "description": "The ad account has no pixel (`reason_code: no_pixel`): `POST /ads/conversion-sync/data-sources/website/create-pixel` would create one and connect it."
              }
            },
            "required": [
              "kind",
              "state",
              "reason_code",
              "message",
              "dataset",
              "can_create",
              "can_turn_off",
              "notice",
              "candidates",
              "can_create_pixel"
            ],
            "description": "Step 2 — the website pixel: receives every outcome without the ad click, matched by the hashed phone and e-mail."
          },
          "instagram": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "website",
                  "instagram"
                ]
              },
              "state": {
                "type": "string",
                "enum": [
                  "coming_soon",
                  "unknown",
                  "prerequisites_missing",
                  "ready",
                  "created",
                  "sending",
                  "off",
                  "error"
                ],
                "description": "`coming_soon` = the measurement engine cannot do this step yet («Pronto»); `unknown` = the engine could not be read now; `prerequisites_missing` = connect Meta Ads (and WhatsApp for the WhatsApp step, Instagram for the Instagram step) first; `ready` = the press would act; `created` = wired but no enabled Meta rule yet, so nothing flows; `sending` = the engine confirms an enabled destination AND an enabled Meta rule; `off` = turned off, receives nothing; `error` = see `reason_code` / `message`."
              },
              "reason_code": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "entitlement_required",
                  "duplicate_kind",
                  "access_missing",
                  "dataset_unreadable",
                  "already_exists",
                  "no_pixel",
                  "pixels_unavailable",
                  "pixel_already_attached",
                  "instagram_facebook_login_required",
                  "meta_refused",
                  "meta_unavailable",
                  "engine_error"
                ]
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "What to do, in Spanish, when `reason_code` is set."
              },
              "dataset": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "Meta’s dataset / pixel id."
                  },
                  "id_tail": {
                    "type": "string"
                  },
                  "export_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "Only what happens from this instant on is sent; never history."
                  },
                  "destination_enabled": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "access_granted": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "shared": {
                    "type": "boolean",
                    "description": "Another workspace already sends to this ad account or dataset."
                  }
                },
                "required": [
                  "external_id",
                  "id_tail",
                  "export_from",
                  "destination_enabled",
                  "access_granted",
                  "shared"
                ]
              },
              "can_create": {
                "type": "boolean"
              },
              "can_turn_off": {
                "type": "boolean"
              },
              "notice": {
                "type": "object",
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "points": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Read before this step’s confirmation, verbatim (Spanish)."
                  },
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 of the points; sent back with the press."
                  }
                },
                "required": [
                  "version",
                  "points",
                  "hash"
                ]
              }
            },
            "required": [
              "kind",
              "state",
              "reason_code",
              "message",
              "dataset",
              "can_create",
              "can_turn_off",
              "notice"
            ],
            "description": "Step 3 — the dataset linked to the Instagram account: receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id and the business account id (never message content). Measurement only: Meta offers no Purchase optimisation on Instagram. Until it is created those outcomes reach the website pixel as before."
          },
          "click_less_not_sent": {
            "type": "boolean",
            "description": "Step 1 is wired and no pixel is: the outcomes without the ad click (most payments) are not sent to Meta at all."
          },
          "preview": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "window_days": {
                "type": "integer"
              },
              "total": {
                "type": "integer"
              },
              "with_ad_click": {
                "type": "integer"
              },
              "without_ad_click": {
                "type": "integer"
              },
              "click_less_to": {
                "type": "string",
                "enum": [
                  "website_pixel",
                  "not_sent"
                ],
                "description": "Where click-less events go: the website pixel (with a non-messaging action_source) or, without one, nowhere."
              },
              "enabled_meta_rules": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "source": {
                "type": "string",
                "enum": [
                  "engine",
                  "vitrina"
                ]
              },
              "truncated": {
                "type": "boolean"
              },
              "stages": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "stage": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string",
                      "description": "The stage in the workspace’s own words."
                    },
                    "on": {
                      "type": "boolean",
                      "description": "Switched on in «Envío a Meta» (it ships when its rule does)."
                    },
                    "total": {
                      "type": "integer"
                    },
                    "with_ad_click": {
                      "type": "integer",
                      "description": "Of `total`, those carrying the click-to-WhatsApp ad click (`ctwa_clid`): the ones the WhatsApp dataset can credit to an ad."
                    },
                    "weekly": {
                      "type": "number"
                    },
                    "weekly_with_ad_click": {
                      "type": "number"
                    },
                    "measured_weeks": {
                      "type": "integer"
                    },
                    "measured_days": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "stage",
                    "label",
                    "on",
                    "total",
                    "with_ad_click",
                    "weekly",
                    "weekly_with_ad_click",
                    "measured_weeks",
                    "measured_days"
                  ]
                }
              }
            },
            "required": [
              "window_days",
              "total",
              "with_ad_click",
              "without_ad_click",
              "click_less_to",
              "enabled_meta_rules",
              "source",
              "truncated",
              "stages"
            ],
            "description": "What would have flowed over the last 30 days. Nothing is sent by reading it."
          }
        },
        "required": [
          "prerequisites",
          "whatsapp",
          "website",
          "instagram",
          "click_less_not_sent",
          "preview"
        ]
      },
      "PaymentInstrumentRequirement": {
        "type": "object",
        "properties": {
          "instrument": {
            "type": "string",
            "enum": [
              "efectivo",
              "transferencia",
              "cheque",
              "vale_vista",
              "tarjeta",
              "tarjeta_debito",
              "tarjeta_credito",
              "bono",
              "otro",
              "mercadopago",
              "webpay",
              "fintoc",
              "flow",
              "khipu",
              "bancame"
            ]
          },
          "required_fields": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "bank",
                "account_number",
                "document_number",
                "note"
              ]
            },
            "description": "Fields that must be non-blank for this instrument. Empty = only paid_on and amount_clp."
          }
        },
        "required": [
          "instrument",
          "required_fields"
        ]
      },
      "AdOrigin": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "platform": {
            "type": "string",
            "enum": [
              "whatsapp",
              "instagram",
              "messenger"
            ],
            "description": "Where the thread lives — the conversation channel."
          },
          "kind": {
            "type": "string",
            "enum": [
              "ad",
              "post"
            ],
            "description": "`post` when the person tapped a boosted post, else `ad`."
          },
          "ad_external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta's ad id; `null` for a post or when Meta sent none."
          },
          "ad_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad name in the ad account; `null` when unknown."
          },
          "campaign_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad's campaign name; `null` when unknown."
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad's headline — the ADVERTISER'S copy, never the customer's words."
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Still image of the creative: our durable copy when `media_archived`, else Meta’s own link."
          },
          "media_archived": {
            "type": "boolean",
            "description": "`true` when `thumbnail_url` is Vitrina’s durable copy; `false` when it is a Meta CDN link that expires (render with a fallback) or absent."
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The creative’s video when a video ad (a Meta CDN link — it expires)."
          },
          "source_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Permalink to the ad or the post it promotes, when known."
          },
          "captured_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the thread opened from the ad (the conversation creation time)."
          }
        },
        "required": [
          "platform",
          "kind",
          "ad_external_id",
          "ad_name",
          "campaign_name",
          "title",
          "thumbnail_url",
          "media_archived",
          "video_url",
          "source_url",
          "captured_at"
        ]
      },
      "ConversationCampaignOrigin": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "campaign_id": {
            "type": "string",
            "format": "uuid"
          },
          "campaign_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "started_by_campaign": {
            "type": "boolean",
            "description": "The send CREATED this thread (born closed, `source = campaign`)."
          },
          "awaiting_reply": {
            "type": "boolean",
            "description": "The newest message in the thread is that send — «Enviada · sin respuesta»."
          }
        },
        "required": [
          "campaign_id",
          "campaign_name",
          "sent_at",
          "started_by_campaign",
          "awaiting_reply"
        ],
        "description": "List rows only: the newest campaign send stored in the thread (`null` when none)."
      },
      "ConversationReplyVia": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp"
            ]
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The contact's most recent open WhatsApp conversation in this workspace — answer there. `null`: none yet; open one with `POST /conversations/whatsapp` and send a template through `POST /conversations/{id}/templates`."
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contact_phone": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "channel",
          "conversation_id",
          "contact_id",
          "contact_phone"
        ],
        "description": "Non-null ONLY when this conversation cannot carry a reply (a website form, `website`, or a Yapo lead, `yapo`): every send on it answers 409 `CHANNEL_READ_ONLY`, and this names the WhatsApp thread to use instead. Absent on every channel that can reply."
      },
      "MessageAttachment": {
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Position in `media_urls`."
          },
          "url": {
            "type": "string",
            "description": "`media_urls[index]`, verbatim."
          },
          "mime": {
            "type": [
              "string",
              "null"
            ],
            "description": "MIME of the original when the row records it (`metadata.mime` on a one-file row); null otherwise."
          },
          "playback_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A URL iOS can play for this audio attachment, or null (not audio, or the AAC rendition is not ready yet — ask `GET /messages/{id}/audio-playback?attachment={index}`)."
          },
          "playback_mime": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "audio/mp4",
              "audio/mpeg",
              "audio/aac"
            ],
            "description": "`audio/mp4` for an AAC rendition (or an mp4/m4a original), `audio/mpeg` / `audio/aac` for an mp3 / aac original served as is; null exactly when `playback_url` is."
          }
        },
        "required": [
          "index",
          "url",
          "mime",
          "playback_url",
          "playback_mime"
        ]
      },
      "MessageForm": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "highlight": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string"
              },
              "value": {
                "type": "string"
              }
            },
            "required": [
              "label",
              "value"
            ],
            "description": "The headline figure, e.g. «Cotización» · «hasta $500.000»."
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "label": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              },
              "required": [
                "label",
                "value"
              ]
            }
          }
        },
        "required": [
          "fields"
        ]
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "conversation_id": {
            "type": "string"
          },
          "sender_type": {
            "type": "string",
            "enum": [
              "contact",
              "ai_agent",
              "human_user",
              "system",
              "api_key"
            ]
          },
          "sender_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "contact id / ai_agent id / member user id / api_key id; null for system."
          },
          "sender_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name of the sender (a member's profile name, the AI agent's, the API key's name when it sent). Prefer `author`."
          },
          "sender_avatar": {
            "type": [
              "string",
              "null"
            ]
          },
          "author": {
            "$ref": "#/components/schemas/Author"
          },
          "content": {
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "type": [
              "string",
              "null"
            ]
          },
          "media_urls": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MessageAttachment"
            },
            "description": "One entry per `media_urls` entry, same order — see MessageAttachment."
          },
          "metadata": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "form": {
                "$ref": "#/components/schemas/MessageForm"
              }
            },
            "description": "Channel-specific extras. `form` is set on the first message of a website-form conversation (see MessageForm)."
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "conversation_id",
          "sender_type",
          "sender_id",
          "author",
          "content",
          "type",
          "created_at"
        ]
      },
      "PendingApproval": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "pending_approval"
                ]
              },
              "action_id": {
                "type": "string",
                "format": "uuid"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "status",
              "action_id",
              "message"
            ]
          }
        },
        "required": [
          "data"
        ]
      },
      "OutboundVerdictError": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "OUTBOUND_BLOCKED",
                  "OUTBOUND_WARNING"
                ]
              },
              "message": {
                "type": "string"
              },
              "reasons": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "contact_blocked",
                    "contact_spam",
                    "channel_suppressed",
                    "scope_blocked",
                    "marketing_consent_missing",
                    "email_not_subscribed",
                    "outbound_hold_active",
                    "window_closed",
                    "template_missing",
                    "template_not_approved",
                    "template_unsupported",
                    "template_params_incomplete",
                    "template_category_unknown",
                    "template_category_mismatch",
                    "legal_basis_mismatch",
                    "no_channel_identity",
                    "account_disconnected",
                    "account_quality_red",
                    "past_max_lateness",
                    "quiet_hours",
                    "conversation_human_owned",
                    "account_quality_degraded",
                    "conversation_closed",
                    "contact_archived",
                    "contact_merged",
                    "stale_context",
                    "input_unresolved",
                    "loop_same_content",
                    "loop_burst",
                    "copy_address_blocked"
                  ]
                }
              },
              "hint": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "properties": {
                  "reasons": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "contact_blocked",
                            "contact_spam",
                            "channel_suppressed",
                            "scope_blocked",
                            "marketing_consent_missing",
                            "email_not_subscribed",
                            "outbound_hold_active",
                            "window_closed",
                            "template_missing",
                            "template_not_approved",
                            "template_unsupported",
                            "template_params_incomplete",
                            "template_category_unknown",
                            "template_category_mismatch",
                            "legal_basis_mismatch",
                            "no_channel_identity",
                            "account_disconnected",
                            "account_quality_red",
                            "past_max_lateness",
                            "quiet_hours",
                            "conversation_human_owned",
                            "account_quality_degraded",
                            "conversation_closed",
                            "contact_archived",
                            "contact_merged",
                            "stale_context",
                            "input_unresolved",
                            "loop_same_content",
                            "loop_burst",
                            "copy_address_blocked"
                          ]
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "bloqueo",
                            "advertencia"
                          ]
                        },
                        "hint": {
                          "type": "string"
                        },
                        "detail": {
                          "type": "object",
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "code",
                        "kind",
                        "hint",
                        "detail"
                      ]
                    }
                  }
                },
                "required": [
                  "reasons"
                ]
              },
              "requestId": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "message",
              "reasons",
              "hint",
              "details"
            ]
          }
        },
        "required": [
          "error"
        ]
      },
      "SequenceMessageStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "situation": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "message"
            ]
          },
          "position": {
            "type": "integer"
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "same_chat",
              "instagram",
              "email",
              "call",
              "sms"
            ]
          },
          "fallback_channel": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "whatsapp",
              "same_chat",
              "instagram",
              "email",
              "call",
              "sms"
            ]
          },
          "wait": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "duration"
                    ]
                  },
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "minutes",
                      "hours",
                      "days"
                    ]
                  },
                  "from": {
                    "type": "string",
                    "enum": [
                      "previous_step",
                      "start"
                    ]
                  },
                  "from_step": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "hours": {
                    "type": "number"
                  }
                },
                "required": [
                  "type",
                  "value",
                  "unit",
                  "from",
                  "from_step",
                  "hours"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "event"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "agreed_time"
                    ]
                  }
                },
                "required": [
                  "type"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "before_appointment"
                    ]
                  },
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "minutes",
                      "hours",
                      "days"
                    ]
                  },
                  "hours": {
                    "type": "number"
                  }
                },
                "required": [
                  "type",
                  "value",
                  "unit",
                  "hours"
                ],
                "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
              }
            ]
          },
          "wait_after_previous": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "value": {
                "type": "number"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "minutes",
                  "hours",
                  "days"
                ]
              }
            },
            "required": [
              "value",
              "unit"
            ]
          },
          "at_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "mode": {
            "type": "string",
            "enum": [
              "auto",
              "auto_unless_assigned",
              "approval",
              "skip"
            ]
          },
          "mode_effective": {
            "type": "string",
            "enum": [
              "auto",
              "auto_unless_assigned",
              "approval",
              "skip"
            ]
          },
          "sends": {
            "type": "boolean"
          },
          "not_sent_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "needs_template": {
            "type": "boolean"
          },
          "content": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "ai_message"
                    ]
                  },
                  "template_after_window": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "type",
                  "template_after_window"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "template_ladder"
                    ]
                  },
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "language": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "name",
                        "language"
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "templates"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "email"
                    ]
                  },
                  "subject": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "custom_body": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "type",
                  "subject",
                  "custom_body"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "promise"
                    ]
                  },
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "trigger_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "followup_id",
                  "trigger_key"
                ]
              }
            ]
          },
          "config_ref": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "recontacto"
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "unanswered_message",
                      "stalled",
                      "never_replied",
                      "lost"
                    ]
                  },
                  "step": {
                    "type": "integer"
                  },
                  "after_hours": {
                    "type": "number"
                  },
                  "unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "d",
                      "h"
                    ]
                  },
                  "autonomy": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "assignee",
                      "approval"
                    ]
                  },
                  "channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "whatsapp",
                      "instagram",
                      "email",
                      "voice"
                    ]
                  },
                  "min_score": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "type",
                  "rail",
                  "step",
                  "after_hours",
                  "unit",
                  "autonomy",
                  "channel",
                  "min_score"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "first_touch"
                    ]
                  },
                  "source": {
                    "type": "string"
                  },
                  "config_origin": {
                    "type": "string",
                    "enum": [
                      "configured",
                      "inherited",
                      "platform_default"
                    ]
                  }
                },
                "required": [
                  "type",
                  "source",
                  "config_origin"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "followup"
                    ]
                  },
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "trigger_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "followup_id",
                  "trigger_key"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "custom"
                    ]
                  },
                  "sequence_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "sequence_id"
                ]
              }
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "sent_30d": {
                "type": "integer"
              },
              "reply_rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1
              },
              "daily_14d": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "waiting_now",
              "sent_30d",
              "reply_rate_30d",
              "daily_14d"
            ]
          }
        },
        "required": [
          "id",
          "key",
          "situation",
          "kind",
          "position",
          "channel",
          "fallback_channel",
          "wait",
          "wait_after_previous",
          "at_hours",
          "mode",
          "mode_effective",
          "sends",
          "not_sent_reason",
          "needs_template",
          "content",
          "config_ref",
          "warnings",
          "stats"
        ]
      },
      "SequenceBranch": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60
          },
          "label": {
            "type": "string",
            "maxLength": 120
          },
          "to": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "A step key, `end` (the sequence ends), or null = the next step by position."
          }
        },
        "required": [
          "key",
          "label",
          "to"
        ]
      },
      "SequenceConditionStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "situation": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "condition"
            ]
          },
          "position": {
            "type": "integer"
          },
          "label": {
            "type": "string"
          },
          "wait": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "duration"
                    ]
                  },
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "minutes",
                      "hours",
                      "days"
                    ]
                  },
                  "from": {
                    "type": "string",
                    "enum": [
                      "previous_step",
                      "start"
                    ]
                  },
                  "from_step": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "hours": {
                    "type": "number"
                  }
                },
                "required": [
                  "type",
                  "value",
                  "unit",
                  "from",
                  "from_step",
                  "hours"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "event"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "agreed_time"
                    ]
                  }
                },
                "required": [
                  "type"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "before_appointment"
                    ]
                  },
                  "value": {
                    "type": "number"
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "minutes",
                      "hours",
                      "days"
                    ]
                  },
                  "hours": {
                    "type": "number"
                  }
                },
                "required": [
                  "type",
                  "value",
                  "unit",
                  "hours"
                ],
                "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
              }
            ]
          },
          "wait_after_previous": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "value": {
                "type": "number"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "minutes",
                  "hours",
                  "days"
                ]
              }
            },
            "required": [
              "value",
              "unit"
            ]
          },
          "at_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "test": {
            "type": "string",
            "enum": [
              "replied",
              "paid",
              "booked",
              "ai_decides",
              "call_outcome"
            ],
            "description": "`replied` (the customer wrote since our last message), `paid` (unavailable until app#6456), `booked` (an appointment since entering), `ai_decides` (the sequence agent picks a branch; `default_branch` on failure). `call_outcome` (how the latest AI call step ended: one branch per outcome answered | promised_pay | promised_visit | call_later | no_answer | do_not_contact, 2 to 6, the branch key is the outcome; `default_branch` for an outcome with no branch). The fixed tests (replied, paid, booked) have exactly the branches `yes` and `no`."
          },
          "ai_prompt": {
            "type": [
              "string",
              "null"
            ]
          },
          "branches": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceBranch"
            }
          },
          "default_branch": {
            "type": [
              "string",
              "null"
            ]
          },
          "config_ref": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "recontacto"
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "unanswered_message",
                      "stalled",
                      "never_replied",
                      "lost"
                    ]
                  },
                  "step": {
                    "type": "integer"
                  },
                  "after_hours": {
                    "type": "number"
                  },
                  "unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "d",
                      "h"
                    ]
                  },
                  "autonomy": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "assignee",
                      "approval"
                    ]
                  },
                  "channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "whatsapp",
                      "instagram",
                      "email",
                      "voice"
                    ]
                  },
                  "min_score": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "type",
                  "rail",
                  "step",
                  "after_hours",
                  "unit",
                  "autonomy",
                  "channel",
                  "min_score"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "first_touch"
                    ]
                  },
                  "source": {
                    "type": "string"
                  },
                  "config_origin": {
                    "type": "string",
                    "enum": [
                      "configured",
                      "inherited",
                      "platform_default"
                    ]
                  }
                },
                "required": [
                  "type",
                  "source",
                  "config_origin"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "followup"
                    ]
                  },
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "trigger_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "followup_id",
                  "trigger_key"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "custom"
                    ]
                  },
                  "sequence_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "sequence_id"
                ]
              }
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "sent_30d": {
                "type": "integer"
              },
              "reply_rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1
              },
              "daily_14d": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "waiting_now",
              "sent_30d",
              "reply_rate_30d",
              "daily_14d"
            ]
          }
        },
        "required": [
          "id",
          "key",
          "situation",
          "kind",
          "position",
          "label",
          "wait",
          "wait_after_previous",
          "at_hours",
          "test",
          "ai_prompt",
          "branches",
          "default_branch",
          "config_ref",
          "warnings",
          "stats"
        ]
      },
      "SequenceWaitUntil": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "date_field"
                ]
              },
              "field": {
                "type": "string",
                "enum": [
                  "promised_date"
                ]
              }
            },
            "required": [
              "kind",
              "field"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "event"
                ]
              },
              "event": {
                "type": "string",
                "enum": [
                  "paid"
                ]
              }
            },
            "required": [
              "kind",
              "event"
            ]
          }
        ],
        "description": "`date_field promised_date`: the day set with POST /sequences/{id}/enrollments/{enrollment_id}/promised-date. `event paid`: unavailable until app#6456."
      },
      "SequenceWaitUntilStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "situation": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "wait_until"
            ]
          },
          "position": {
            "type": "integer"
          },
          "label": {
            "type": "string"
          },
          "until": {
            "$ref": "#/components/schemas/SequenceWaitUntil"
          },
          "timeout": {
            "type": "object",
            "properties": {
              "value": {
                "type": "number"
              },
              "unit": {
                "type": "string",
                "enum": [
                  "minutes",
                  "hours",
                  "days"
                ]
              }
            },
            "required": [
              "value",
              "unit"
            ]
          },
          "at_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "config_ref": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "recontacto"
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "unanswered_message",
                      "stalled",
                      "never_replied",
                      "lost"
                    ]
                  },
                  "step": {
                    "type": "integer"
                  },
                  "after_hours": {
                    "type": "number"
                  },
                  "unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "d",
                      "h"
                    ]
                  },
                  "autonomy": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "assignee",
                      "approval"
                    ]
                  },
                  "channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "whatsapp",
                      "instagram",
                      "email",
                      "voice"
                    ]
                  },
                  "min_score": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "type",
                  "rail",
                  "step",
                  "after_hours",
                  "unit",
                  "autonomy",
                  "channel",
                  "min_score"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "first_touch"
                    ]
                  },
                  "source": {
                    "type": "string"
                  },
                  "config_origin": {
                    "type": "string",
                    "enum": [
                      "configured",
                      "inherited",
                      "platform_default"
                    ]
                  }
                },
                "required": [
                  "type",
                  "source",
                  "config_origin"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "followup"
                    ]
                  },
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "trigger_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "followup_id",
                  "trigger_key"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "custom"
                    ]
                  },
                  "sequence_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "sequence_id"
                ]
              }
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "sent_30d": {
                "type": "integer"
              },
              "reply_rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1
              },
              "daily_14d": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "waiting_now",
              "sent_30d",
              "reply_rate_30d",
              "daily_14d"
            ]
          }
        },
        "required": [
          "id",
          "key",
          "situation",
          "kind",
          "position",
          "label",
          "until",
          "timeout",
          "at_hours",
          "config_ref",
          "warnings",
          "stats"
        ]
      },
      "SequenceGotoStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "situation": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "goto"
            ]
          },
          "position": {
            "type": "integer"
          },
          "label": {
            "type": "string"
          },
          "target_step": {
            "type": "string"
          },
          "max_times": {
            "type": "integer",
            "minimum": 1,
            "maximum": 5
          },
          "at_hours": {
            "type": [
              "number",
              "null"
            ]
          },
          "config_ref": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "recontacto"
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "unanswered_message",
                      "stalled",
                      "never_replied",
                      "lost"
                    ]
                  },
                  "step": {
                    "type": "integer"
                  },
                  "after_hours": {
                    "type": "number"
                  },
                  "unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "d",
                      "h"
                    ]
                  },
                  "autonomy": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "assignee",
                      "approval"
                    ]
                  },
                  "channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "whatsapp",
                      "instagram",
                      "email",
                      "voice"
                    ]
                  },
                  "min_score": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "type",
                  "rail",
                  "step",
                  "after_hours",
                  "unit",
                  "autonomy",
                  "channel",
                  "min_score"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "first_touch"
                    ]
                  },
                  "source": {
                    "type": "string"
                  },
                  "config_origin": {
                    "type": "string",
                    "enum": [
                      "configured",
                      "inherited",
                      "platform_default"
                    ]
                  }
                },
                "required": [
                  "type",
                  "source",
                  "config_origin"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "followup"
                    ]
                  },
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "trigger_key": {
                    "type": "string"
                  }
                },
                "required": [
                  "type",
                  "followup_id",
                  "trigger_key"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "custom"
                    ]
                  },
                  "sequence_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "sequence_id"
                ]
              }
            ]
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "sent_30d": {
                "type": "integer"
              },
              "reply_rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1
              },
              "daily_14d": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "waiting_now",
              "sent_30d",
              "reply_rate_30d",
              "daily_14d"
            ]
          }
        },
        "required": [
          "id",
          "key",
          "situation",
          "kind",
          "position",
          "label",
          "target_step",
          "max_times",
          "at_hours",
          "config_ref",
          "warnings",
          "stats"
        ]
      },
      "SequenceStep": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/SequenceMessageStep"
          },
          {
            "$ref": "#/components/schemas/SequenceConditionStep"
          },
          {
            "$ref": "#/components/schemas/SequenceWaitUntilStep"
          },
          {
            "$ref": "#/components/schemas/SequenceGotoStep"
          }
        ],
        "discriminator": {
          "propertyName": "kind",
          "mapping": {
            "message": "#/components/schemas/SequenceMessageStep",
            "condition": "#/components/schemas/SequenceConditionStep",
            "wait_until": "#/components/schemas/SequenceWaitUntilStep",
            "goto": "#/components/schemas/SequenceGotoStep"
          }
        }
      },
      "Sequence": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "builtin",
              "custom"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "draft"
            ]
          },
          "entry": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "lead_arrived",
                      "customer_went_quiet",
                      "customer_asked_reminder",
                      "stage_changed",
                      "quoted_not_booked",
                      "appointment_booked",
                      "signed_up_not_bought",
                      "installment_overdue"
                    ]
                  },
                  "filters": {
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "maxLength": 40,
                              "description": "Custom sequences filter on `interest`, `source`, `stage` (stage id) or `tag` (tag name)."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "in"
                              ]
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "maxLength": 200
                              },
                              "maxItems": 50
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "values"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": [
                                "days_overdue",
                                "amount"
                              ],
                              "description": "Only for the `installment_overdue` entry: `days_overdue` (days past the due day) or `amount` (pesos still owed), at least `value`."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "gte"
                              ]
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "value"
                          ]
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "event",
                  "filters"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "list"
                    ]
                  },
                  "list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "list_id"
                ]
              }
            ]
          },
          "goal": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "reply",
                  "promise_kept",
                  "books_visit",
                  "books_demo",
                  "pays"
                ]
              },
              "label": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "label"
            ]
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "collection": {
            "type": "boolean",
            "description": "A collection sequence (Cobranza): it obeys the workspace’s collection rules (`GET /sequences/collection-rules`) and needs a recorded legal review to publish (`POST /sequences/{id}/legal-review`). True for the «Cobranza de cuotas» template, an entry of `installment_overdue` or a goal of `pays`."
          },
          "priority": {
            "type": "integer"
          },
          "situations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "enabled": {
                  "type": "boolean"
                },
                "first_step": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "waiting_now": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "notes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": [
                "key",
                "name",
                "enabled",
                "first_step",
                "waiting_now",
                "notes"
              ]
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceStep"
            }
          },
          "connections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "next",
                    "reply_exit",
                    "end",
                    "branch",
                    "loop"
                  ]
                },
                "situation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Drawn on the arrow (vitrina-app#6454): the branch label, the loop bound, «Después de 2 vueltas»."
                },
                "branch": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "`branch` connections: the condition branch key."
                },
                "max_times": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`loop` connections: the goto bound."
                }
              },
              "required": [
                "from",
                "to",
                "kind",
                "situation"
              ]
            }
          },
          "window_markers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "situation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "after_step": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "situation",
                "after_step",
                "label"
              ]
            }
          },
          "ends": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "situation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "mark_lost",
                    "finish",
                    "continue",
                    "handoff"
                  ]
                },
                "after": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "value": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string",
                      "enum": [
                        "minutes",
                        "hours",
                        "days"
                      ]
                    }
                  },
                  "required": [
                    "value",
                    "unit"
                  ]
                },
                "at_hours": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "detail": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "continues_in": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "sequence_key": {
                      "type": "string"
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "sequence_key",
                    "situation"
                  ]
                }
              },
              "required": [
                "key",
                "situation",
                "type",
                "after",
                "at_hours",
                "label",
                "detail",
                "continues_in"
              ]
            }
          },
          "stats": {
            "type": "object",
            "properties": {
              "waiting_now": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "sent_30d": {
                "type": "integer"
              },
              "reply_rate_30d": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1
              },
              "daily_14d": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            },
            "required": [
              "waiting_now",
              "sent_30d",
              "reply_rate_30d",
              "daily_14d"
            ]
          },
          "published_version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The newest published version number; null until the first draft (app#6448)."
          },
          "draft": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version_id": {
                "type": "string",
                "format": "uuid"
              },
              "change_count": {
                "type": "integer"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "version_id",
              "change_count",
              "updated_at"
            ],
            "description": "The open draft, if any («Borrador · N cambios»)."
          }
        },
        "required": [
          "id",
          "key",
          "kind",
          "name",
          "description",
          "status",
          "entry",
          "goal",
          "agent_id",
          "collection",
          "priority",
          "situations",
          "steps",
          "connections",
          "window_markers",
          "ends",
          "stats"
        ]
      },
      "SequenceCallOutcome": {
        "type": "string",
        "enum": [
          "answered",
          "promised_pay",
          "promised_visit",
          "call_later",
          "no_answer",
          "do_not_contact"
        ],
        "description": "AI call steps only: the outcome the scenario supplied (default no_answer)."
      },
      "SequenceSimulation": {
        "type": "object",
        "properties": {
          "timeline": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "step_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "step_key": {
                  "type": "string"
                },
                "channel": {
                  "type": "string",
                  "enum": [
                    "whatsapp",
                    "same_chat",
                    "instagram",
                    "email",
                    "call",
                    "sms"
                  ]
                },
                "planned_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "shifted_at": {
                  "type": "string",
                  "format": "date-time"
                },
                "shift_reason": {
                  "type": "string",
                  "enum": [
                    "outside_contact_hours",
                    "previous_late",
                    "unanswered_hold",
                    "agreed_reminder",
                    "min_gap",
                    "max_per_day",
                    "automated_gap",
                    "person_ceiling",
                    "appointment_soon",
                    "daily_cap",
                    "non_collection_day",
                    "holiday",
                    "outside_collection_hours",
                    "min_spacing",
                    "calls_per_day",
                    "calls_per_week",
                    "messages_per_week"
                  ]
                },
                "skip_reason": {
                  "type": "string",
                  "enum": [
                    "non_collection_day",
                    "holiday",
                    "outside_collection_hours",
                    "min_spacing",
                    "calls_per_day",
                    "calls_per_week",
                    "messages_per_week"
                  ],
                  "description": "Why the collection rules skipped the step (status `skipped_by_collection_rules`)."
                },
                "preview": {
                  "type": "string"
                },
                "call_outcome": {
                  "$ref": "#/components/schemas/SequenceCallOutcome"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "sends",
                    "awaits_approval",
                    "skipped_no_channel",
                    "skipped_no_template",
                    "skipped_by_collection_rules"
                  ]
                },
                "iteration": {
                  "type": "integer",
                  "minimum": 2,
                  "description": "Custom sequences: 2 on the first lap back through a loop, 3 on the next (absent = first pass)."
                }
              },
              "required": [
                "step_id",
                "step_key",
                "channel",
                "planned_at",
                "shifted_at",
                "status"
              ]
            }
          },
          "end": {
            "type": "object",
            "properties": {
              "reason": {
                "type": "string",
                "enum": [
                  "replied",
                  "paid",
                  "end_of_sequence",
                  "marked_lost",
                  "no_fitting_approved_message",
                  "no_channel_left",
                  "blocked_by_collection_rules",
                  "message_limit_reached",
                  "opted_out"
                ]
              },
              "at_step": {
                "type": "string",
                "format": "uuid"
              },
              "at": {
                "type": "string",
                "format": "date-time"
              }
            },
            "required": [
              "reason",
              "at"
            ]
          },
          "notes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "path": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "step_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "step_key": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "message",
                    "condition",
                    "wait_until",
                    "goto"
                  ]
                },
                "iteration": {
                  "type": "integer",
                  "minimum": 1
                },
                "at_hours": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "branch": {
                  "type": "string"
                },
                "decided_by": {
                  "type": "string",
                  "enum": [
                    "scenario",
                    "decision",
                    "default"
                  ]
                },
                "jumped": {
                  "type": "boolean"
                },
                "resolved_by": {
                  "type": "string",
                  "enum": [
                    "promised_date",
                    "event",
                    "timeout"
                  ]
                }
              },
              "required": [
                "step_id",
                "step_key",
                "kind",
                "iteration",
                "at_hours"
              ]
            },
            "description": "Custom sequences with branches, waits until or loops (vitrina-app#6454): every step the person passes, in order, with the branch taken, what ended a wait, and whether a goto jumped."
          }
        },
        "required": [
          "timeline",
          "end",
          "notes"
        ]
      },
      "SequenceSimulateRequest": {
        "type": "object",
        "properties": {
          "contact_id": {
            "type": "string",
            "format": "uuid",
            "description": "A real contact: WhatsApp and opted-out are read from it."
          },
          "persona": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "maxLength": 200
              },
              "has_whatsapp": {
                "type": "boolean"
              },
              "has_email": {
                "type": "boolean"
              },
              "opted_out": {
                "type": "boolean"
              }
            },
            "required": [
              "has_whatsapp",
              "opted_out"
            ],
            "description": "A test person. Exactly one of contact_id or persona."
          },
          "last_message_at": {
            "type": "string",
            "format": "date-time",
            "description": "When our last message to the person went out. Default: now."
          },
          "situation": {
            "type": "string",
            "enum": [
              "conversed",
              "never_replied",
              "lost"
            ],
            "description": "Which path of «Si no responde». Default: derived from the contact (conversed when they ever wrote), else never_replied."
          },
          "behavior": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "never_replies"
                    ]
                  }
                },
                "required": [
                  "kind"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "replies_after_step"
                    ]
                  },
                  "step": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50
                  }
                },
                "required": [
                  "kind",
                  "step"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "pays"
                    ]
                  },
                  "at_step": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 50
                  }
                },
                "required": [
                  "kind",
                  "at_step"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "promises_date"
                    ]
                  },
                  "date": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "kind",
                  "date"
                ]
              }
            ]
          },
          "version": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "`published` (default), `draft` (the open draft) or a version id from `GET /sequences/{id}/versions`."
          },
          "call_outcomes": {
            "type": "object",
            "additionalProperties": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/SequenceCallOutcome"
                },
                {
                  "description": "How an AI call step ended: answered | promised_pay | promised_visit | call_later | no_answer | do_not_contact."
                }
              ]
            },
            "description": "AI call steps: how each call ends in this scenario, keyed by step id. A call step not named ends `no_answer`. Nothing is called and no model runs; `do_not_contact` ends the walk at that call."
          },
          "decisions": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "description": "Custom sequences: the branch each condition takes, `{ <step key or step id>: <branch key> }`. «La IA decide» is never called in a simulation; without an entry it takes its first branch. Other conditions follow `behavior` unless listed here."
          }
        },
        "required": [
          "behavior"
        ],
        "additionalProperties": false
      },
      "SequenceDefinitionStep": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "situation": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120
              },
              "kind": {
                "type": "string",
                "enum": [
                  "message"
                ]
              },
              "position": {
                "type": "integer",
                "minimum": 1
              },
              "channel": {
                "type": "string",
                "enum": [
                  "whatsapp",
                  "same_chat",
                  "instagram",
                  "email",
                  "call",
                  "sms"
                ]
              },
              "fallback_channel": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "whatsapp",
                  "same_chat",
                  "instagram",
                  "email",
                  "call",
                  "sms"
                ]
              },
              "wait": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "duration"
                        ]
                      },
                      "value": {
                        "type": "number"
                      },
                      "unit": {
                        "type": "string",
                        "enum": [
                          "minutes",
                          "hours",
                          "days"
                        ]
                      },
                      "from": {
                        "type": "string",
                        "enum": [
                          "previous_step",
                          "start"
                        ]
                      },
                      "from_step": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "hours": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "type",
                      "value",
                      "unit",
                      "from",
                      "from_step",
                      "hours"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "event"
                        ]
                      },
                      "event": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "type",
                      "event"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "agreed_time"
                        ]
                      }
                    },
                    "required": [
                      "type"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "before_appointment"
                        ]
                      },
                      "value": {
                        "type": "number"
                      },
                      "unit": {
                        "type": "string",
                        "enum": [
                          "minutes",
                          "hours",
                          "days"
                        ]
                      },
                      "hours": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "type",
                      "value",
                      "unit",
                      "hours"
                    ],
                    "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
                  }
                ]
              },
              "mode": {
                "type": "string",
                "enum": [
                  "auto",
                  "auto_unless_assigned",
                  "approval",
                  "skip"
                ]
              },
              "content": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "ai_message"
                        ]
                      },
                      "template_after_window": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "type",
                      "template_after_window"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "template_ladder"
                        ]
                      },
                      "templates": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "language": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "name",
                            "language"
                          ]
                        }
                      }
                    },
                    "required": [
                      "type",
                      "templates"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "email"
                        ]
                      },
                      "subject": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "custom_body": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "type",
                      "subject",
                      "custom_body"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "promise"
                        ]
                      },
                      "followup_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "trigger_key": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "type",
                      "followup_id",
                      "trigger_key"
                    ]
                  }
                ]
              },
              "config_ref": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "recontacto"
                        ]
                      },
                      "rail": {
                        "type": "string",
                        "enum": [
                          "unanswered_message",
                          "stalled",
                          "never_replied",
                          "lost"
                        ]
                      },
                      "step": {
                        "type": "integer"
                      },
                      "after_hours": {
                        "type": "number"
                      },
                      "unit": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "d",
                          "h"
                        ]
                      },
                      "autonomy": {
                        "type": "string",
                        "enum": [
                          "auto",
                          "assignee",
                          "approval"
                        ]
                      },
                      "channel": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "whatsapp",
                          "instagram",
                          "email",
                          "voice"
                        ]
                      },
                      "min_score": {
                        "type": [
                          "number",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "type",
                      "rail",
                      "step",
                      "after_hours",
                      "unit",
                      "autonomy",
                      "channel",
                      "min_score"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "first_touch"
                        ]
                      },
                      "source": {
                        "type": "string"
                      },
                      "config_origin": {
                        "type": "string",
                        "enum": [
                          "configured",
                          "inherited",
                          "platform_default"
                        ]
                      }
                    },
                    "required": [
                      "type",
                      "source",
                      "config_origin"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "followup"
                        ]
                      },
                      "followup_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "trigger_key": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "type",
                      "followup_id",
                      "trigger_key"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "custom"
                        ]
                      },
                      "sequence_id": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "required": [
                      "type",
                      "sequence_id"
                    ]
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Where a built-in step is stored (keep it as read); null for a step added in the draft."
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120,
                "description": "Custom sequences: the step key (or `end`) this step goes to; absent/null = the next step by position."
              }
            },
            "required": [
              "key",
              "situation",
              "kind",
              "position",
              "channel",
              "fallback_channel",
              "wait",
              "mode",
              "content",
              "config_ref"
            ]
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "situation": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120
              },
              "kind": {
                "type": "string",
                "enum": [
                  "condition"
                ]
              },
              "position": {
                "type": "integer",
                "minimum": 1
              },
              "wait": {
                "oneOf": [
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "duration"
                        ]
                      },
                      "value": {
                        "type": "number"
                      },
                      "unit": {
                        "type": "string",
                        "enum": [
                          "minutes",
                          "hours",
                          "days"
                        ]
                      },
                      "from": {
                        "type": "string",
                        "enum": [
                          "previous_step",
                          "start"
                        ]
                      },
                      "from_step": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "hours": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "type",
                      "value",
                      "unit",
                      "from",
                      "from_step",
                      "hours"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "event"
                        ]
                      },
                      "event": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "type",
                      "event"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "agreed_time"
                        ]
                      }
                    },
                    "required": [
                      "type"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "before_appointment"
                        ]
                      },
                      "value": {
                        "type": "number"
                      },
                      "unit": {
                        "type": "string",
                        "enum": [
                          "minutes",
                          "hours",
                          "days"
                        ]
                      },
                      "hours": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "type",
                      "value",
                      "unit",
                      "hours"
                    ],
                    "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
                  }
                ]
              },
              "test": {
                "type": "string",
                "enum": [
                  "replied",
                  "paid",
                  "booked",
                  "ai_decides",
                  "call_outcome"
                ],
                "description": "`replied` (the customer wrote since our last message), `paid` (unavailable until app#6456), `booked` (an appointment since entering), `ai_decides` (the sequence agent picks a branch; `default_branch` on failure). `call_outcome` (how the latest AI call step ended: one branch per outcome answered | promised_pay | promised_visit | call_later | no_answer | do_not_contact, 2 to 6, the branch key is the outcome; `default_branch` for an outcome with no branch). The fixed tests (replied, paid, booked) have exactly the branches `yes` and `no`."
              },
              "ai_prompt": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 2000,
                "default": null
              },
              "branches": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/SequenceBranch"
                },
                "minItems": 1,
                "maxItems": 6
              },
              "default_branch": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 60,
                "default": null
              },
              "config_ref": {
                "type": "null"
              }
            },
            "required": [
              "key",
              "situation",
              "kind",
              "position",
              "wait",
              "test",
              "branches"
            ]
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "situation": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120
              },
              "kind": {
                "type": "string",
                "enum": [
                  "wait_until"
                ]
              },
              "position": {
                "type": "integer",
                "minimum": 1
              },
              "until": {
                "$ref": "#/components/schemas/SequenceWaitUntil"
              },
              "timeout": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 2160
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "minutes",
                      "hours",
                      "days"
                    ]
                  }
                },
                "required": [
                  "value",
                  "unit"
                ]
              },
              "config_ref": {
                "type": "null"
              },
              "next": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120,
                "description": "Custom sequences: the step key (or `end`) this step goes to; absent/null = the next step by position."
              }
            },
            "required": [
              "key",
              "situation",
              "kind",
              "position",
              "until",
              "timeout"
            ]
          },
          {
            "type": "object",
            "properties": {
              "key": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "situation": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 120
              },
              "kind": {
                "type": "string",
                "enum": [
                  "goto"
                ]
              },
              "position": {
                "type": "integer",
                "minimum": 1
              },
              "target_step": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "max_times": {
                "type": "integer",
                "minimum": 1,
                "maximum": 5,
                "default": 2,
                "description": "How many times one person takes this jump (default 2, at most 5)."
              },
              "config_ref": {
                "type": "null"
              }
            },
            "required": [
              "key",
              "situation",
              "kind",
              "position",
              "target_step"
            ]
          }
        ],
        "description": "A step: `message` (any sequence), or — custom sequences only (vitrina-app#6454) — `condition`, `wait_until`, `goto`. A built-in refuses the last three (400)."
      },
      "SequenceDefinition": {
        "type": "object",
        "properties": {
          "schema_version": {
            "type": "number",
            "enum": [
              1
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "entry": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "lead_arrived",
                      "customer_went_quiet",
                      "customer_asked_reminder",
                      "stage_changed",
                      "quoted_not_booked",
                      "appointment_booked",
                      "signed_up_not_bought",
                      "installment_overdue"
                    ]
                  },
                  "filters": {
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "maxLength": 40,
                              "description": "Custom sequences filter on `interest`, `source`, `stage` (stage id) or `tag` (tag name)."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "in"
                              ]
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "maxLength": 200
                              },
                              "maxItems": 50
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "values"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": [
                                "days_overdue",
                                "amount"
                              ],
                              "description": "Only for the `installment_overdue` entry: `days_overdue` (days past the due day) or `amount` (pesos still owed), at least `value`."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "gte"
                              ]
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "value"
                          ]
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "event",
                  "filters"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "list"
                    ]
                  },
                  "list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "list_id"
                ]
              }
            ]
          },
          "goal": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "reply",
                  "promise_kept",
                  "books_visit",
                  "books_demo",
                  "pays"
                ]
              }
            },
            "required": [
              "type"
            ]
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "collection": {
            "type": "boolean",
            "description": "Mark the sequence as a collection sequence. Optional (false when left out); an entry of `installment_overdue` or a goal of `pays` makes it one anyway."
          },
          "priority": {
            "type": "integer"
          },
          "situations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 120
                },
                "enabled": {
                  "type": "boolean"
                }
              },
              "required": [
                "key",
                "name",
                "enabled"
              ]
            },
            "maxItems": 50
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceDefinitionStep"
            },
            "maxItems": 100
          },
          "connections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "from": {
                  "type": "string"
                },
                "to": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "next",
                    "reply_exit",
                    "end",
                    "branch",
                    "loop"
                  ]
                },
                "situation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "label": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Drawn on the arrow (vitrina-app#6454): the branch label, the loop bound, «Después de 2 vueltas»."
                },
                "branch": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "`branch` connections: the condition branch key."
                },
                "max_times": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "`loop` connections: the goto bound."
                }
              },
              "required": [
                "from",
                "to",
                "kind",
                "situation"
              ]
            }
          },
          "ends": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "situation": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "mark_lost",
                    "finish",
                    "continue",
                    "handoff"
                  ]
                },
                "after": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "value": {
                      "type": "number"
                    },
                    "unit": {
                      "type": "string",
                      "enum": [
                        "minutes",
                        "hours",
                        "days"
                      ]
                    }
                  },
                  "required": [
                    "value",
                    "unit"
                  ]
                },
                "continues_in": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "sequence_key": {
                      "type": "string"
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "sequence_key",
                    "situation"
                  ]
                }
              },
              "required": [
                "key",
                "situation",
                "type",
                "after",
                "continues_in"
              ]
            }
          },
          "settings": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "recontacto"
                    ]
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "situations": {
                        "type": "object",
                        "properties": {
                          "never_replied": {
                            "type": "object",
                            "properties": {
                              "enabled": {
                                "type": "boolean"
                              },
                              "lose_after_days": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "enabled",
                              "lose_after_days"
                            ]
                          },
                          "stalled": {
                            "type": "object",
                            "properties": {
                              "enabled": {
                                "type": "boolean"
                              },
                              "lose_after_days": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "enabled",
                              "lose_after_days"
                            ]
                          },
                          "lost": {
                            "type": "object",
                            "properties": {
                              "enabled": {
                                "type": "boolean"
                              },
                              "lose_after_days": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "enabled",
                              "lose_after_days"
                            ]
                          }
                        },
                        "required": [
                          "never_replied",
                          "stalled",
                          "lost"
                        ]
                      },
                      "unanswered_message": {
                        "type": "object",
                        "properties": {
                          "enabled": {
                            "type": "boolean"
                          },
                          "conversation_lifetime_cap": {
                            "type": "integer"
                          },
                          "template_after_window": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "enabled",
                          "conversation_lifetime_cap",
                          "template_after_window"
                        ]
                      },
                      "pacing": {
                        "type": "object",
                        "additionalProperties": {}
                      },
                      "after_handoff": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "required": [
                      "enabled",
                      "situations",
                      "unanswered_message",
                      "pacing",
                      "after_handoff"
                    ],
                    "description": "The «Si no responde» settings that are not a step: the same keys as `GET /followups/recontacto` (daily_cap, daily_review_budget, pacing, after_handoff, …), minus the ladders (the steps hold them), the chat reminders’ autonomy (their `mode`) and the platform’s stamps."
                  }
                },
                "required": [
                  "type",
                  "config"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "first_touch"
                    ]
                  },
                  "daily_cap": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "sources": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "configured": {
                          "type": "boolean"
                        },
                        "account_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "templates": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 200
                              },
                              "language": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 20
                              },
                              "params": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "maxLength": 80
                                },
                                "maxItems": 20
                              },
                              "header_param": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "maxLength": 80
                              }
                            },
                            "required": [
                              "name",
                              "language",
                              "params",
                              "header_param"
                            ]
                          },
                          "maxItems": 20
                        },
                        "templates_by_intent": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "name": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 200
                                },
                                "language": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 20
                                },
                                "params": {
                                  "type": "array",
                                  "items": {
                                    "type": "string",
                                    "maxLength": 80
                                  },
                                  "maxItems": 20
                                },
                                "header_param": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "maxLength": 80
                                }
                              },
                              "required": [
                                "name",
                                "language",
                                "params",
                                "header_param"
                              ]
                            },
                            "maxItems": 20
                          }
                        },
                        "email_account_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "email_subject": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 300
                        },
                        "email_body": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 10000
                        }
                      },
                      "required": [
                        "configured",
                        "account_id",
                        "templates",
                        "templates_by_intent",
                        "email_account_id",
                        "email_subject",
                        "email_body"
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "daily_cap",
                  "sources"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "followup"
                    ]
                  }
                },
                "required": [
                  "type"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "none"
                    ]
                  }
                },
                "required": [
                  "type"
                ]
              }
            ]
          }
        },
        "required": [
          "schema_version",
          "name",
          "description",
          "entry",
          "goal",
          "agent_id",
          "priority",
          "situations",
          "steps",
          "connections",
          "ends",
          "settings"
        ]
      },
      "SequenceDefinitionView": {
        "type": "object",
        "properties": {
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence_key": {
            "type": "string"
          },
          "version_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Null for the published version of a built-in that was never versioned (today’s config)."
          },
          "version": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null for a draft (numbered on publish) or when unversioned."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "definition": {
            "$ref": "#/components/schemas/SequenceDefinition"
          }
        },
        "required": [
          "sequence_id",
          "sequence_key",
          "version_id",
          "version",
          "status",
          "definition"
        ]
      },
      "SequenceDraft": {
        "type": "object",
        "properties": {
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence_key": {
            "type": "string"
          },
          "version_id": {
            "type": "string",
            "format": "uuid"
          },
          "based_on_version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "change_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "created_by": {
            "type": "object",
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          },
          "updated_by": {
            "type": "object",
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          },
          "definition": {
            "$ref": "#/components/schemas/SequenceDefinition"
          },
          "sequence": {
            "$ref": "#/components/schemas/Sequence"
          }
        },
        "required": [
          "sequence_id",
          "sequence_key",
          "version_id",
          "based_on_version",
          "change_count",
          "created_at",
          "updated_at",
          "created_by",
          "updated_by",
          "definition",
          "sequence"
        ]
      },
      "SequenceChange": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "description": "Dotted address: `steps.stalled-2.wait`, `situations.lost.enabled`, `settings.pacing.max_per_day`."
          },
          "kind": {
            "type": "string",
            "enum": [
              "added",
              "removed",
              "changed"
            ]
          },
          "step_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "situation": {
            "type": [
              "string",
              "null"
            ]
          },
          "before": {},
          "after": {},
          "people_affected_now": {
            "type": [
              "integer",
              "null"
            ],
            "description": "People at that step (or in that situation, or in the sequence) right now; null when the sequence keeps no count («Al llegar»)."
          }
        },
        "required": [
          "field",
          "kind",
          "step_key",
          "situation",
          "people_affected_now"
        ]
      },
      "SequenceDraftDiff": {
        "type": "object",
        "properties": {
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "version_id": {
            "type": "string",
            "format": "uuid"
          },
          "base_version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "change_count": {
            "type": "integer"
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceChange"
            }
          }
        },
        "required": [
          "sequence_id",
          "version_id",
          "base_version",
          "change_count",
          "changes"
        ]
      },
      "SequenceValidationProblem": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string"
          },
          "step_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": "string"
          },
          "fix": {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "draft_template",
                  "connect_channel",
                  "request_review",
                  "remove_step",
                  "change_channel",
                  "pick_agent",
                  "contact_support"
                ]
              },
              "target": {
                "$ref": "#/components/schemas/NotificationTarget"
              }
            },
            "required": [
              "kind"
            ]
          }
        },
        "required": [
          "code",
          "step_id",
          "message",
          "fix"
        ]
      },
      "SequenceDraftValidation": {
        "type": "object",
        "properties": {
          "blockers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceValidationProblem"
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceValidationProblem"
            }
          }
        },
        "required": [
          "blockers",
          "warnings"
        ]
      },
      "CollectionRules": {
        "type": "object",
        "properties": {
          "weekdays": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7
            },
            "minItems": 1,
            "maxItems": 7,
            "description": "Days collection may contact, ISO (1 = Monday … 7 = Sunday). Sunday is never allowed."
          },
          "hours": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
              },
              "end": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
              }
            },
            "required": [
              "start",
              "end"
            ],
            "description": "Local window (workspace time zone), start inclusive, end exclusive."
          },
          "skip_holidays": {
            "type": "boolean",
            "description": "National public holidays never allow a contact. Must be true."
          },
          "calls_per_day": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "calls_per_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "messages_per_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 14
          },
          "min_spacing_hours": {
            "type": "integer",
            "minimum": 0,
            "maximum": 720
          },
          "over_cap": {
            "type": "string",
            "enum": [
              "skip",
              "move"
            ],
            "description": "`skip` drops a step that would go over a cap; `move` pushes it to the next week."
          }
        },
        "required": [
          "weekdays",
          "hours",
          "skip_holidays",
          "calls_per_day",
          "calls_per_week",
          "messages_per_week",
          "min_spacing_hours",
          "over_cap"
        ]
      },
      "CollectionRulesView": {
        "type": "object",
        "properties": {
          "rules": {
            "$ref": "#/components/schemas/CollectionRules"
          },
          "version": {
            "type": "integer",
            "description": "0 = the platform defaults, never saved. Goes up on every real change; a legal review names the version it reviewed."
          },
          "is_default": {
            "type": "boolean"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_by": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          },
          "ceilings": {
            "type": "object",
            "properties": {
              "weekdays": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "hours": {
                "type": "object",
                "properties": {
                  "start": {
                    "type": "string",
                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                  },
                  "end": {
                    "type": "string",
                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                  }
                },
                "required": [
                  "start",
                  "end"
                ]
              },
              "calls_per_day": {
                "type": "integer"
              },
              "calls_per_week": {
                "type": "integer"
              },
              "messages_per_week": {
                "type": "integer"
              },
              "min_spacing_hours": {
                "type": "integer"
              }
            },
            "required": [
              "weekdays",
              "hours",
              "calls_per_day",
              "calls_per_week",
              "messages_per_week",
              "min_spacing_hours"
            ]
          }
        },
        "required": [
          "rules",
          "version",
          "is_default",
          "updated_at",
          "updated_by",
          "ceilings"
        ]
      },
      "CollectionRulesReplaceRequest": {
        "type": "object",
        "properties": {
          "weekdays": {
            "type": "array",
            "items": {
              "type": "integer",
              "minimum": 1,
              "maximum": 7
            },
            "minItems": 1,
            "maxItems": 7,
            "description": "Days collection may contact, ISO (1 = Monday … 7 = Sunday). Sunday is never allowed."
          },
          "hours": {
            "type": "object",
            "properties": {
              "start": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
              },
              "end": {
                "type": "string",
                "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
              }
            },
            "required": [
              "start",
              "end"
            ],
            "description": "Local window (workspace time zone), start inclusive, end exclusive."
          },
          "skip_holidays": {
            "type": "boolean",
            "description": "National public holidays never allow a contact. Must be true."
          },
          "calls_per_day": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "calls_per_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10
          },
          "messages_per_week": {
            "type": "integer",
            "minimum": 0,
            "maximum": 14
          },
          "min_spacing_hours": {
            "type": "integer",
            "minimum": 0,
            "maximum": 720
          },
          "over_cap": {
            "type": "string",
            "enum": [
              "skip",
              "move"
            ],
            "description": "`skip` drops a step that would go over a cap; `move` pushes it to the next week."
          }
        },
        "additionalProperties": false
      },
      "SequenceLegalReview": {
        "type": "object",
        "properties": {
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "required": {
            "type": "boolean",
            "description": "True for a collection sequence."
          },
          "status": {
            "type": "string",
            "enum": [
              "not_required",
              "missing",
              "valid",
              "outdated"
            ]
          },
          "rules_version": {
            "type": "integer"
          },
          "review": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "reviewer_member_id": {
                "type": "string",
                "format": "uuid"
              },
              "reviewed_on": {
                "type": "string"
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "attachment_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rules_version": {
                "type": "integer"
              },
              "created_at": {
                "type": "string"
              },
              "created_by": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "kind": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "via": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "kind",
                  "id"
                ]
              }
            },
            "required": [
              "id",
              "reviewer_member_id",
              "reviewed_on",
              "note",
              "attachment_url",
              "rules_version",
              "created_at",
              "created_by"
            ]
          }
        },
        "required": [
          "sequence_id",
          "required",
          "status",
          "rules_version",
          "review"
        ]
      },
      "SequenceLegalReviewRequest": {
        "type": "object",
        "properties": {
          "reviewer_member_id": {
            "type": "string",
            "format": "uuid"
          },
          "reviewed_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Day of the review (YYYY-MM-DD). Default today; not in the future."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          },
          "attachment_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "format": "uri"
          },
          "rules_version": {
            "type": "integer",
            "minimum": 0,
            "description": "The rules version the reviewer looked at. 409 `rules_version_stale` when the rules changed since; default the current version."
          }
        },
        "required": [
          "reviewer_member_id"
        ],
        "additionalProperties": false
      },
      "SequencePublishResult": {
        "type": "object",
        "properties": {
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "version_id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer"
          },
          "published_at": {
            "type": "string"
          },
          "origin": {
            "type": "string",
            "enum": [
              "edit",
              "revert",
              "materialized",
              "external"
            ]
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceChange"
            }
          },
          "sequence": {
            "$ref": "#/components/schemas/Sequence"
          }
        },
        "required": [
          "sequence_id",
          "version_id",
          "version",
          "published_at",
          "origin",
          "changes",
          "sequence"
        ]
      },
      "SequenceVersion": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published",
              "archived"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "edit",
              "revert",
              "materialized",
              "external"
            ]
          },
          "based_on_version": {
            "type": [
              "integer",
              "null"
            ]
          },
          "reverted_from": {
            "type": [
              "integer",
              "null"
            ]
          },
          "change_count": {
            "type": "integer"
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SequenceChange"
            }
          },
          "created_at": {
            "type": "string"
          },
          "created_by": {
            "type": "object",
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_by": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "via": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          }
        },
        "required": [
          "id",
          "version",
          "status",
          "origin",
          "based_on_version",
          "reverted_from",
          "change_count",
          "changes",
          "created_at",
          "created_by",
          "published_at",
          "published_by"
        ]
      },
      "SequenceTemplate": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "blank",
              "quote_followup",
              "reactivate",
              "signup_not_paid",
              "installments",
              "appointment_reminder"
            ]
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "entry": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "lead_arrived",
                      "customer_went_quiet",
                      "customer_asked_reminder",
                      "stage_changed",
                      "quoted_not_booked",
                      "appointment_booked",
                      "signed_up_not_bought",
                      "installment_overdue"
                    ]
                  },
                  "filters": {
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "maxLength": 40,
                              "description": "Custom sequences filter on `interest`, `source`, `stage` (stage id) or `tag` (tag name)."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "in"
                              ]
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "maxLength": 200
                              },
                              "maxItems": 50
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "values"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": [
                                "days_overdue",
                                "amount"
                              ],
                              "description": "Only for the `installment_overdue` entry: `days_overdue` (days past the due day) or `amount` (pesos still owed), at least `value`."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "gte"
                              ]
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "value"
                          ]
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "event",
                  "filters"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "list"
                    ]
                  },
                  "list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "list_id"
                ]
              }
            ]
          },
          "goal": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "reply",
                  "promise_kept",
                  "books_visit",
                  "books_demo",
                  "pays"
                ]
              },
              "label": {
                "type": "string"
              }
            },
            "required": [
              "type",
              "label"
            ]
          },
          "channels": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "whatsapp",
                "same_chat",
                "instagram",
                "email",
                "call",
                "sms"
              ]
            }
          },
          "steps": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "message"
                      ]
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "channel": {
                      "type": "string",
                      "enum": [
                        "whatsapp",
                        "same_chat",
                        "instagram",
                        "email",
                        "call",
                        "sms"
                      ]
                    },
                    "fallback_channel": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "whatsapp",
                        "same_chat",
                        "instagram",
                        "email",
                        "call",
                        "sms"
                      ]
                    },
                    "wait": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "duration"
                              ]
                            },
                            "value": {
                              "type": "number"
                            },
                            "unit": {
                              "type": "string",
                              "enum": [
                                "minutes",
                                "hours",
                                "days"
                              ]
                            },
                            "from": {
                              "type": "string",
                              "enum": [
                                "previous_step",
                                "start"
                              ]
                            },
                            "from_step": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "hours": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "type",
                            "value",
                            "unit",
                            "from",
                            "from_step",
                            "hours"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "event"
                              ]
                            },
                            "event": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "type",
                            "event"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "agreed_time"
                              ]
                            }
                          },
                          "required": [
                            "type"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "before_appointment"
                              ]
                            },
                            "value": {
                              "type": "number"
                            },
                            "unit": {
                              "type": "string",
                              "enum": [
                                "minutes",
                                "hours",
                                "days"
                              ]
                            },
                            "hours": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "type",
                            "value",
                            "unit",
                            "hours"
                          ],
                          "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
                        }
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "auto",
                        "auto_unless_assigned",
                        "approval",
                        "skip"
                      ]
                    },
                    "content": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "ai_message"
                              ]
                            },
                            "template_after_window": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "type",
                            "template_after_window"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "template_ladder"
                              ]
                            },
                            "templates": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "name",
                                  "language"
                                ]
                              }
                            }
                          },
                          "required": [
                            "type",
                            "templates"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "email"
                              ]
                            },
                            "subject": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "custom_body": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "type",
                            "subject",
                            "custom_body"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "promise"
                              ]
                            },
                            "followup_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "trigger_key": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "type",
                            "followup_id",
                            "trigger_key"
                          ]
                        }
                      ]
                    },
                    "config_ref": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "recontacto"
                              ]
                            },
                            "rail": {
                              "type": "string",
                              "enum": [
                                "unanswered_message",
                                "stalled",
                                "never_replied",
                                "lost"
                              ]
                            },
                            "step": {
                              "type": "integer"
                            },
                            "after_hours": {
                              "type": "number"
                            },
                            "unit": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "d",
                                "h"
                              ]
                            },
                            "autonomy": {
                              "type": "string",
                              "enum": [
                                "auto",
                                "assignee",
                                "approval"
                              ]
                            },
                            "channel": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "whatsapp",
                                "instagram",
                                "email",
                                "voice"
                              ]
                            },
                            "min_score": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "type",
                            "rail",
                            "step",
                            "after_hours",
                            "unit",
                            "autonomy",
                            "channel",
                            "min_score"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "first_touch"
                              ]
                            },
                            "source": {
                              "type": "string"
                            },
                            "config_origin": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "inherited",
                                "platform_default"
                              ]
                            }
                          },
                          "required": [
                            "type",
                            "source",
                            "config_origin"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "followup"
                              ]
                            },
                            "followup_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "trigger_key": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "type",
                            "followup_id",
                            "trigger_key"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "custom"
                              ]
                            },
                            "sequence_id": {
                              "type": "string",
                              "format": "uuid"
                            }
                          },
                          "required": [
                            "type",
                            "sequence_id"
                          ]
                        },
                        {
                          "type": "null"
                        }
                      ],
                      "description": "Where a built-in step is stored (keep it as read); null for a step added in the draft."
                    },
                    "next": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120,
                      "description": "Custom sequences: the step key (or `end`) this step goes to; absent/null = the next step by position."
                    },
                    "label": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "situation",
                    "kind",
                    "position",
                    "channel",
                    "fallback_channel",
                    "wait",
                    "mode",
                    "content",
                    "config_ref",
                    "label"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "condition"
                      ]
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "wait": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "duration"
                              ]
                            },
                            "value": {
                              "type": "number"
                            },
                            "unit": {
                              "type": "string",
                              "enum": [
                                "minutes",
                                "hours",
                                "days"
                              ]
                            },
                            "from": {
                              "type": "string",
                              "enum": [
                                "previous_step",
                                "start"
                              ]
                            },
                            "from_step": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "hours": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "type",
                            "value",
                            "unit",
                            "from",
                            "from_step",
                            "hours"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "event"
                              ]
                            },
                            "event": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "type",
                            "event"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "agreed_time"
                              ]
                            }
                          },
                          "required": [
                            "type"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "before_appointment"
                              ]
                            },
                            "value": {
                              "type": "number"
                            },
                            "unit": {
                              "type": "string",
                              "enum": [
                                "minutes",
                                "hours",
                                "days"
                              ]
                            },
                            "hours": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "type",
                            "value",
                            "unit",
                            "hours"
                          ],
                          "description": "Custom sequences: this long before the booked appointment (a reminder). Only with entry `appointment_booked`."
                        }
                      ]
                    },
                    "test": {
                      "type": "string",
                      "enum": [
                        "replied",
                        "paid",
                        "booked",
                        "ai_decides",
                        "call_outcome"
                      ],
                      "description": "`replied` (the customer wrote since our last message), `paid` (unavailable until app#6456), `booked` (an appointment since entering), `ai_decides` (the sequence agent picks a branch; `default_branch` on failure). `call_outcome` (how the latest AI call step ended: one branch per outcome answered | promised_pay | promised_visit | call_later | no_answer | do_not_contact, 2 to 6, the branch key is the outcome; `default_branch` for an outcome with no branch). The fixed tests (replied, paid, booked) have exactly the branches `yes` and `no`."
                    },
                    "ai_prompt": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 2000,
                      "default": null
                    },
                    "branches": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SequenceBranch"
                      },
                      "minItems": 1,
                      "maxItems": 6
                    },
                    "default_branch": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 60,
                      "default": null
                    },
                    "config_ref": {
                      "type": "null"
                    },
                    "label": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "situation",
                    "kind",
                    "position",
                    "wait",
                    "test",
                    "branches",
                    "label"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "wait_until"
                      ]
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "until": {
                      "$ref": "#/components/schemas/SequenceWaitUntil"
                    },
                    "timeout": {
                      "type": "object",
                      "properties": {
                        "value": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 2160
                        },
                        "unit": {
                          "type": "string",
                          "enum": [
                            "minutes",
                            "hours",
                            "days"
                          ]
                        }
                      },
                      "required": [
                        "value",
                        "unit"
                      ]
                    },
                    "config_ref": {
                      "type": "null"
                    },
                    "next": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120,
                      "description": "Custom sequences: the step key (or `end`) this step goes to; absent/null = the next step by position."
                    },
                    "label": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "situation",
                    "kind",
                    "position",
                    "until",
                    "timeout",
                    "label"
                  ]
                },
                {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "situation": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 120
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "goto"
                      ]
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 1
                    },
                    "target_step": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "max_times": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 5,
                      "default": 2,
                      "description": "How many times one person takes this jump (default 2, at most 5)."
                    },
                    "config_ref": {
                      "type": "null"
                    },
                    "label": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "key",
                    "situation",
                    "kind",
                    "position",
                    "target_step",
                    "label"
                  ]
                }
              ]
            }
          }
        },
        "required": [
          "key",
          "name",
          "description",
          "entry",
          "goal",
          "channels",
          "steps"
        ]
      },
      "SequenceCreateResult": {
        "type": "object",
        "properties": {
          "sequence": {
            "$ref": "#/components/schemas/Sequence"
          },
          "draft": {
            "$ref": "#/components/schemas/SequenceDraft"
          }
        },
        "required": [
          "sequence",
          "draft"
        ]
      },
      "SequenceCreateRequest": {
        "type": "object",
        "properties": {
          "template": {
            "type": "string",
            "enum": [
              "blank",
              "quote_followup",
              "reactivate",
              "signup_not_paid",
              "installments",
              "appointment_reminder"
            ],
            "description": "A template from `GET /sequences/templates` (its suggested steps become the first draft); omitted = `blank` (from scratch)."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "entry": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "lead_arrived",
                      "customer_went_quiet",
                      "customer_asked_reminder",
                      "stage_changed",
                      "quoted_not_booked",
                      "appointment_booked",
                      "signed_up_not_bought",
                      "installment_overdue"
                    ]
                  },
                  "filters": {
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "maxLength": 40,
                              "description": "Custom sequences filter on `interest`, `source`, `stage` (stage id) or `tag` (tag name)."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "in"
                              ]
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "maxLength": 200
                              },
                              "maxItems": 50
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "values"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": [
                                "days_overdue",
                                "amount"
                              ],
                              "description": "Only for the `installment_overdue` entry: `days_overdue` (days past the due day) or `amount` (pesos still owed), at least `value`."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "gte"
                              ]
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "value"
                          ]
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "event",
                  "filters"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "list"
                    ]
                  },
                  "list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "list_id"
                ]
              }
            ]
          },
          "goal": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "reply",
                  "promise_kept",
                  "books_visit",
                  "books_demo",
                  "pays"
                ]
              }
            },
            "required": [
              "type"
            ]
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The AI agent that runs this sequence: writes its messages with its own persona and knowledge, and answers when the person replies. Must be an active agent of the workspace. `null` = the workspace's current agent."
          }
        },
        "additionalProperties": false
      },
      "SequencePatchRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "entry": {
            "oneOf": [
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "event"
                    ]
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "lead_arrived",
                      "customer_went_quiet",
                      "customer_asked_reminder",
                      "stage_changed",
                      "quoted_not_booked",
                      "appointment_booked",
                      "signed_up_not_bought",
                      "installment_overdue"
                    ]
                  },
                  "filters": {
                    "type": "array",
                    "items": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "maxLength": 40,
                              "description": "Custom sequences filter on `interest`, `source`, `stage` (stage id) or `tag` (tag name)."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "in"
                              ]
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "maxLength": 200
                              },
                              "maxItems": 50
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "values"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "enum": [
                                "days_overdue",
                                "amount"
                              ],
                              "description": "Only for the `installment_overdue` entry: `days_overdue` (days past the due day) or `amount` (pesos still owed), at least `value`."
                            },
                            "op": {
                              "type": "string",
                              "enum": [
                                "gte"
                              ]
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "field",
                            "op",
                            "value"
                          ]
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "type",
                  "event",
                  "filters"
                ]
              },
              {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "list"
                    ]
                  },
                  "list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "type",
                  "list_id"
                ]
              }
            ]
          },
          "goal": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "reply",
                  "promise_kept",
                  "books_visit",
                  "books_demo",
                  "pays"
                ]
              }
            },
            "required": [
              "type"
            ]
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The AI agent that runs this sequence: writes its messages with its own persona and knowledge, and answers when the person replies. Must be an active agent of the workspace. `null` = the workspace's current agent."
          }
        },
        "additionalProperties": false
      },
      "SequenceDeleteResult": {
        "type": "object",
        "properties": {
          "deleted": {
            "type": "boolean"
          },
          "exited": {
            "type": "integer"
          }
        },
        "required": [
          "deleted",
          "exited"
        ]
      },
      "SequenceOrder": {
        "type": "object",
        "properties": {
          "order": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "key": {
                  "type": "string"
                },
                "priority": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "key",
                "priority"
              ]
            }
          }
        },
        "required": [
          "order"
        ]
      },
      "SequenceOrderRequest": {
        "type": "object",
        "properties": {
          "order": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "minItems": 1,
            "maxItems": 500,
            "description": "Highest priority first: `builtin:si-no-responde` and every custom sequence id, each exactly once."
          }
        },
        "required": [
          "order"
        ],
        "additionalProperties": false
      },
      "SequenceEnrollmentPromise": {
        "type": "object",
        "properties": {
          "enrollment_id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "promised_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "current_step": {
            "type": [
              "string",
              "null"
            ],
            "description": "The step key the person is at."
          },
          "next_due_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "waiting_for_date": {
            "type": "boolean",
            "description": "True when the person is at a «Esperar hasta la fecha prometida» step (it now waits for this date)."
          }
        },
        "required": [
          "enrollment_id",
          "sequence_id",
          "contact_id",
          "promised_date",
          "current_step",
          "next_due_at",
          "waiting_for_date"
        ]
      },
      "SequencePromisedDateRequest": {
        "type": "object",
        "properties": {
          "date": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The day the customer promised (`YYYY-MM-DD`, workspace calendar); null clears it.",
            "example": "2026-11-15"
          }
        },
        "required": [
          "date"
        ],
        "additionalProperties": false
      },
      "SequenceEnrollmentFunnel": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer"
          },
          "reachable": {
            "type": "integer"
          },
          "will_enroll": {
            "type": "integer"
          },
          "excluded": {
            "type": "object",
            "properties": {
              "blocked": {
                "type": "integer"
              },
              "spam": {
                "type": "integer"
              },
              "archived": {
                "type": "integer"
              },
              "no_identifier": {
                "type": "integer"
              },
              "invalid_identifier": {
                "type": "integer"
              },
              "duplicate": {
                "type": "integer"
              },
              "opted_out": {
                "type": "integer"
              },
              "already_in_sequence": {
                "type": "integer"
              },
              "in_higher_sequence": {
                "type": "integer"
              },
              "in_lower_sequence": {
                "type": "integer"
              },
              "in_recontacto": {
                "type": "integer"
              }
            },
            "required": [
              "blocked",
              "spam",
              "archived",
              "no_identifier",
              "invalid_identifier",
              "duplicate",
              "opted_out",
              "already_in_sequence",
              "in_higher_sequence",
              "in_lower_sequence",
              "in_recontacto"
            ]
          },
          "opted_out_by": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          }
        },
        "required": [
          "total",
          "reachable",
          "will_enroll",
          "excluded",
          "opted_out_by"
        ]
      },
      "SequenceEnrollmentBatch": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "list_id": {
            "type": "string",
            "format": "uuid"
          },
          "list_name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_activation",
              "activated",
              "cancelled"
            ]
          },
          "channel": {
            "type": "string",
            "enum": [
              "whatsapp",
              "email"
            ]
          },
          "consent": {
            "type": "object",
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "customer_relationship",
                  "explicit_opt_in",
                  "other_documented"
                ],
                "description": "Why the owner may write to this list: `customer_relationship` (past or current customers), `explicit_opt_in` (they registered and accepted terms, or consented in a form), `other_documented` (anything else, described in the note)."
              },
              "note": {
                "type": "string"
              },
              "by": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "via": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "kind",
                  "id",
                  "via"
                ]
              },
              "at": {
                "type": "string"
              }
            },
            "required": [
              "basis",
              "note",
              "by",
              "at"
            ]
          },
          "funnel": {
            "$ref": "#/components/schemas/SequenceEnrollmentFunnel"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "whatsapp_marketing_cost"
                  ]
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "code",
                "message"
              ]
            }
          },
          "result": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SequenceEnrollmentFunnel"
              },
              {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "enrolled": {
                    "type": "integer"
                  }
                },
                "required": [
                  "enrolled"
                ]
              }
            ]
          },
          "activated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "activated_by": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "kind": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "id"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "sequence_id",
          "list_id",
          "list_name",
          "status",
          "channel",
          "consent",
          "funnel",
          "warnings",
          "result",
          "activated_at",
          "activated_by",
          "created_at"
        ]
      },
      "SequenceEnrollFromListRequest": {
        "type": "object",
        "properties": {
          "list_id": {
            "type": "string",
            "format": "uuid",
            "description": "The list to enroll: the id of a saved audience (`GET /audiences`). A CSV import becomes a list by importing with `tags` and saving an audience on that tag."
          },
          "consent_basis": {
            "type": "string",
            "enum": [
              "customer_relationship",
              "explicit_opt_in",
              "other_documented"
            ],
            "description": "Why the owner may write to this list: `customer_relationship` (past or current customers), `explicit_opt_in` (they registered and accepted terms, or consented in a form), `other_documented` (anything else, described in the note)."
          },
          "note": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500,
            "description": "Evidence of the consent: where and when it was given. Stored with who and when."
          }
        },
        "required": [
          "list_id",
          "consent_basis",
          "note"
        ],
        "additionalProperties": false
      },
      "SequenceEnrollment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "sequence_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "exited",
              "completed"
            ]
          },
          "exit_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "entered_at": {
            "type": "string"
          },
          "exited_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "batch_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "consent": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "basis": {
                "type": "string",
                "enum": [
                  "customer_relationship",
                  "explicit_opt_in",
                  "other_documented"
                ],
                "description": "Why the owner may write to this list: `customer_relationship` (past or current customers), `explicit_opt_in` (they registered and accepted terms, or consented in a form), `other_documented` (anything else, described in the note)."
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "basis",
              "note",
              "at"
            ]
          }
        },
        "required": [
          "id",
          "sequence_id",
          "contact_id",
          "status",
          "exit_reason",
          "entered_at",
          "exited_at",
          "batch_id",
          "consent"
        ]
      },
      "MetaAdsAccount": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "external_account_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivers_spend": {
            "type": "boolean"
          },
          "provider_connection_id": {
            "type": "string",
            "format": "uuid",
            "description": "The id of the provider connection this ad account ships through. Opaque; Vitrina resolves it server-side for every Ads read and write, so a client never needs to send it back."
          },
          "atribu_connection_id": {
            "type": "string",
            "description": "Deprecated: the same value as `provider_connection_id` — read that instead. Kept on the wire for existing clients; removed in a later release.",
            "deprecated": true
          }
        },
        "required": [
          "external_account_id",
          "name",
          "currency",
          "timezone",
          "delivers_spend",
          "provider_connection_id",
          "atribu_connection_id"
        ]
      },
      "MetaAdsCandidate": {
        "type": "object",
        "properties": {
          "candidate_id": {
            "type": "string"
          },
          "external_account_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "account_status": {
            "type": "string"
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "candidate_id",
          "external_account_id",
          "name",
          "currency",
          "account_status",
          "timezone"
        ]
      },
      "MetaAdsIntegration": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string",
            "enum": [
              "meta_ads"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "pending_selection",
              "connected",
              "error",
              "disconnected"
            ]
          },
          "account_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "disconnected_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the connection last stopped being `connected` (disconnected, access revoked, or the token stopped working) — the last day the ad figures are current («cifras hasta el …»). `null` while `connected`, when it was never connected, and for a stop recorded before this field existed."
          },
          "account": {
            "$ref": "#/components/schemas/MetaAdsAccount"
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "reason": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean"
              }
            },
            "required": [
              "reason",
              "retryable"
            ]
          },
          "handoff": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "expires_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "expires_at"
            ]
          },
          "candidates": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/MetaAdsCandidate"
            }
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "provider",
          "status",
          "account_label",
          "connected_at",
          "disconnected_at",
          "account",
          "error",
          "handoff",
          "candidates",
          "updated_at"
        ]
      },
      "MetaAdsConnectResult": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "handoff_id": {
            "type": "string"
          },
          "expires_at": {
            "type": "string"
          }
        },
        "required": [
          "url",
          "handoff_id",
          "expires_at"
        ]
      },
      "MetaAdsConnectBody": {
        "type": "object",
        "properties": {
          "return_to": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "return_to"
        ]
      },
      "MetaAdsReturnBody": {
        "type": "object",
        "properties": {
          "handoff_id": {
            "type": "string",
            "minLength": 1
          },
          "state": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "MetaAdsFinalizeBody": {
        "type": "object",
        "properties": {
          "candidate_id": {
            "type": "string",
            "minLength": 1
          }
        },
        "required": [
          "candidate_id"
        ]
      },
      "MetaAdsPage": {
        "type": "object",
        "properties": {
          "page_external_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "picture_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "instagram_username": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "page_external_id",
          "name",
          "picture_url",
          "instagram_username"
        ]
      },
      "MetaAdsAvailablePage": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MetaAdsPage"
          },
          {
            "type": "object",
            "properties": {
              "connected": {
                "type": "boolean"
              }
            },
            "required": [
              "connected"
            ]
          }
        ]
      },
      "MetaAdsPagesState": {
        "type": "object",
        "properties": {
          "available": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MetaAdsAvailablePage"
            },
            "description": "Every Facebook Page the connected Meta account can see, with whether it is already tracked. Empty while `needs_reconsent`."
          },
          "connected": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MetaAdsPage"
            },
            "description": "The Pages currently tracked, even while `needs_reconsent`."
          },
          "needs_reconsent": {
            "type": "boolean",
            "description": "The Meta grant lacks the Page permissions (or Meta no longer accepts it). Restart `POST /integrations/meta-ads/connect`: the consent asks for them again."
          },
          "reconsent_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "missing_scopes",
              "token_invalid"
            ]
          }
        },
        "required": [
          "available",
          "connected",
          "needs_reconsent",
          "reconsent_reason"
        ]
      },
      "MetaAdsSetPagesResult": {
        "type": "object",
        "properties": {
          "enabled_count": {
            "type": "integer"
          },
          "unavailable_page_external_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Requested Pages the Meta account cannot see; not saved."
          }
        },
        "required": [
          "enabled_count",
          "unavailable_page_external_ids"
        ]
      },
      "MetaAdsSetPagesBody": {
        "type": "object",
        "properties": {
          "page_external_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1
            },
            "maxItems": 50,
            "description": "The Pages to track — exactly this set. Pages left out stop being tracked; an empty list stops tracking all of them."
          }
        },
        "required": [
          "page_external_ids"
        ]
      },
      "MetaAdsAdsNotConnected": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "not_connected"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "whatsapp_not_connected"
            ]
          }
        },
        "required": [
          "state"
        ]
      },
      "MetaAdsAdGreeting": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "autofill_message": {
            "type": [
              "string",
              "null"
            ]
          },
          "ice_breakers": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "landing_screen_type": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "text",
          "autofill_message",
          "ice_breakers",
          "landing_screen_type"
        ]
      },
      "MetaAdsAdRow": {
        "type": "object",
        "properties": {
          "ad_id": {
            "type": "string"
          },
          "ad_name": {
            "type": "string"
          },
          "ad_status": {
            "type": "string"
          },
          "campaign": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "ad_set": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "destination": {
            "type": "object",
            "properties": {
              "type": {
                "type": "string"
              },
              "whatsapp_phone_number": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "page_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "type",
              "whatsapp_phone_number",
              "page_id"
            ]
          },
          "cta_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "greeting": {
            "$ref": "#/components/schemas/MetaAdsAdGreeting"
          },
          "greeting_status": {
            "type": "string",
            "enum": [
              "present",
              "none",
              "unreadable"
            ]
          },
          "checks": {
            "type": "object",
            "properties": {
              "destination_connected": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "greeting_configured": {
                "type": "boolean"
              }
            },
            "required": [
              "destination_connected",
              "greeting_configured"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "ad_id",
          "ad_name",
          "ad_status",
          "campaign",
          "ad_set",
          "destination",
          "cta_type",
          "greeting",
          "greeting_status",
          "checks",
          "last_synced_at"
        ]
      },
      "MetaAdsAdsConnected": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "connected"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MetaAdsAdRow"
            }
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "state",
          "items",
          "next_cursor",
          "last_synced_at"
        ]
      },
      "MetaAdsAdsResponse": {
        "anyOf": [
          {
            "$ref": "#/components/schemas/MetaAdsAdsNotConnected"
          },
          {
            "$ref": "#/components/schemas/MetaAdsAdsConnected"
          }
        ]
      },
      "WooCommerceConnectBody": {
        "type": "object",
        "properties": {
          "store_url": {
            "type": "string",
            "minLength": 3,
            "maxLength": 500
          },
          "return_to": {
            "type": "string",
            "format": "uri"
          }
        },
        "required": [
          "store_url"
        ]
      },
      "WooCommerceSyncBody": {
        "type": "object",
        "properties": {
          "force": {
            "type": "boolean"
          }
        }
      },
      "ExternalStockSourceBody": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "maxLength": 2048,
            "format": "uri"
          },
          "token": {
            "type": "string",
            "minLength": 8,
            "maxLength": 512
          }
        },
        "required": [
          "url"
        ]
      },
      "ProviderConnectionAccount": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "description": "The provider’s own id for the account (GoHighLevel location id, Mercado Pago user id). Opaque; relayed verbatim."
          },
          "name": {
            "type": "string",
            "description": "The account’s display name at the provider."
          }
        },
        "required": [
          "external_id",
          "name"
        ],
        "description": "The connected account. Present on `connected` and `needs_reconnect` (the card must name what needs reconnecting)."
      },
      "ProviderConnectionReason": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "user_cancelled",
              "provider_denied",
              "provider_error",
              "token_exchange_failed",
              "account_connected_elsewhere",
              "no_candidates",
              "connect_failed",
              "internal_error",
              "state_expired",
              "state_invalid",
              "handoff_expired",
              "handoff_used",
              "provider_not_configured",
              "origin_not_allowed",
              "selection_expired",
              "selection_conflict",
              "reconnect_required",
              "sync_error",
              "access_revoked",
              "disconnected_by_user",
              "connection_not_found",
              "handoff_not_found",
              "unknown_error"
            ],
            "description": "A STABLE code — also the copy key. `user_cancelled`, `provider_denied`, `provider_error`, `token_exchange_failed`, `account_connected_elsewhere`, `no_candidates`, `connect_failed`, `internal_error`, `state_expired`, `state_invalid`, `handoff_expired`, `handoff_used`, `provider_not_configured`, `origin_not_allowed` come from the consent itself; `selection_expired`, `selection_conflict`, `reconnect_required`, `sync_error`, `access_revoked`, `disconnected_by_user`, `connection_not_found`, `handoff_not_found`, `unknown_error` are Vitrina’s own."
          },
          "label": {
            "type": "string",
            "description": "A ready Spanish sentence for the card, naming the provider."
          },
          "retryable": {
            "type": "boolean",
            "description": "Is trying the same thing again, right away, likely to work?"
          }
        },
        "required": [
          "code",
          "label",
          "retryable"
        ],
        "description": "Why the card is in this state: a failed first connect, `reconnect_required`, `sync_error` (connected, last sync failed), `access_revoked`, `disconnected_by_user`."
      },
      "ProviderConnection": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "gohighlevel",
              "mercadopago",
              "shopify"
            ]
          },
          "display_name": {
            "type": "string"
          },
          "category": {
            "type": "string",
            "enum": [
              "crm",
              "payments",
              "commerce"
            ]
          },
          "description": {
            "type": "string",
            "description": "One sentence: what connecting it does for the workspace."
          },
          "state": {
            "type": "string",
            "enum": [
              "not_connected",
              "connecting",
              "connected",
              "needs_reconnect",
              "disconnected"
            ],
            "description": "`not_connected` (never connected) → `connecting` (first consent in flight) → `connected` → `needs_reconnect` (the provider stopped honouring the grant; data is not updating) → `disconnected` (was connected, is not any more)."
          },
          "state_label": {
            "type": "string",
            "description": "The state in Spanish, ready to render."
          },
          "account": {
            "$ref": "#/components/schemas/ProviderConnectionAccount"
          },
          "last_account": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ProviderConnectionAccount"
              },
              {
                "description": "On `disconnected`: the account that was connected."
              }
            ]
          },
          "reason": {
            "$ref": "#/components/schemas/ProviderConnectionReason"
          },
          "last_attempt": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ProviderConnectionReason"
              },
              {
                "description": "How the latest attempt ended when it left the state unchanged (a cancelled re-authorization of a connected or needs_reconnect card). A secondary note; `reason` keeps saying why the card is in its state."
              }
            ]
          },
          "next_step": {
            "type": "object",
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "connect",
                  "resume",
                  "choose_account",
                  "reconnect",
                  "retry",
                  "activate_addon",
                  "contact_support",
                  "none"
                ],
                "description": "`connect` / `resume` / `reconnect` / `retry` → `POST …/connect`; `activate_addon` → the Vitrina Ads add-on; `choose_account` → `POST …/finalize`; `contact_support` and `none` call nothing. Disconnect is always available on a card that is not `not_connected`."
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The primary button’s label, or null for `none`."
              }
            },
            "required": [
              "action",
              "label"
            ],
            "description": "The one thing the card should offer next. Never a dead end."
          },
          "attempt": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "step": {
                "type": "string",
                "enum": [
                  "consent",
                  "choose_account"
                ]
              },
              "handoff": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "expires_at": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "expires_at"
                ],
                "description": "The consent this attempt belongs to. On `choose_account`, send its `id` to `/finalize` as `handoff_id`."
              },
              "candidates": {
                "type": [
                  "array",
                  "null"
                ],
                "items": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ProviderConnectionAccount"
                    },
                    {
                      "type": "object"
                    }
                  ]
                },
                "description": "Step `choose_account` only: the accounts the consent can connect. Send one `external_id` to `/finalize`."
              },
              "selection_expires_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "prompt": {
                "type": "string",
                "description": "The picker / waiting title, in Spanish."
              }
            },
            "required": [
              "step",
              "handoff",
              "candidates",
              "selection_expires_at",
              "prompt"
            ],
            "description": "A connect in flight, if any — a consent the admin is on, or an account choice still owed. Rides on top of `state`: reconnecting a connected account does not make the card read `connecting`."
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "disconnected_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the provider’s data last finished syncing."
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "connect_input": {
            "type": "string",
            "enum": [
              "none",
              "shop"
            ],
            "description": "What `POST …/connect` must carry besides `return_to`: `shop` for Shopify (the store’s address), `none` for every other provider."
          },
          "connect_notice": {
            "type": [
              "string",
              "null"
            ],
            "description": "Show this BEFORE sending the page to the provider, or null. Shopify: its consent screen names the connectivity partner’s app, not Vitrina; saying so first keeps the merchant from abandoning the install there. A ready Spanish paragraph."
          },
          "catalog": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "product_count": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Products Vitrina offers from the store after the last sync."
              },
              "variant_count": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "last_sync_at": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "When the last import, reconcile or stock update finished."
              },
              "last_sync_kind": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "full",
                  "delta"
                ],
                "description": "`full` — a walk of the whole store (import, hourly reconcile, «Sincronizar ahora»); `delta` — the products the store changed, pulled seconds after the change."
              },
              "last_sync_status": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "running",
                  "success",
                  "blocked",
                  "error"
                ],
                "description": "`blocked`: the reconcile found many products gone at once and switched none off (the drop guard); `POST …/sync {force:true}` applies it when the store really was emptied."
              },
              "last_sync_error": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "A ready Spanish sentence when the last run did not apply."
              },
              "last_sync_summary": {
                "type": [
                  "object",
                  "null"
                ],
                "additionalProperties": {},
                "description": "Counters of the last run (products, variants, deactivated…)."
              },
              "last_full_sync_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "product_count",
              "variant_count",
              "last_sync_at",
              "last_sync_kind",
              "last_sync_status",
              "last_sync_error",
              "last_sync_summary",
              "last_full_sync_at"
            ],
            "description": "A store (`category: commerce`): its catalog as Vitrina keeps it. Null on every other card."
          },
          "addon_active": {
            "type": "boolean",
            "description": "Whether the Vitrina Ads add-on is active. `false` only on a card that outlived a lapsed add-on (a connection or a consent in flight): it stays visible and can be disconnected, and any step that would start a new consent becomes `activate_addon`."
          }
        },
        "required": [
          "provider",
          "display_name",
          "category",
          "description",
          "state",
          "state_label",
          "account",
          "last_account",
          "reason",
          "last_attempt",
          "next_step",
          "attempt",
          "connected_at",
          "disconnected_at",
          "last_synced_at",
          "updated_at",
          "connect_input",
          "connect_notice",
          "catalog",
          "addon_active"
        ]
      },
      "ProviderConnectResult": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "Send the WHOLE page here: it goes straight to the provider’s consent screen. Single-use, 45 minutes."
          },
          "handoff_id": {
            "type": "string",
            "format": "uuid"
          },
          "expires_at": {
            "type": "string"
          },
          "connection": {
            "$ref": "#/components/schemas/ProviderConnection"
          }
        },
        "required": [
          "url",
          "handoff_id",
          "expires_at",
          "connection"
        ]
      },
      "ProviderConnectBody": {
        "type": "object",
        "properties": {
          "return_to": {
            "type": "string",
            "format": "uri"
          },
          "shop": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300,
            "description": "Shopify only, and required there: the store, as `mi-tienda`, `mi-tienda.myshopify.com` or its admin URL (`https://admin.shopify.com/store/mi-tienda`). A custom storefront domain is refused (400, `details.reason: \"invalid_shop\"`): Shopify installs on the myshopify address. Ignored for every other provider."
          }
        },
        "required": [
          "return_to"
        ]
      },
      "ProviderReturnBody": {
        "type": "object",
        "properties": {
          "handoff_id": {
            "type": "string",
            "format": "uuid"
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "status": {
            "type": "string",
            "enum": [
              "success",
              "pending_selection",
              "failed",
              "cancelled"
            ]
          },
          "reason": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          }
        }
      },
      "ProviderFinalizeBody": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "An `attempt.candidates[].external_id`, verbatim."
          },
          "handoff_id": {
            "type": "string",
            "format": "uuid",
            "description": "The `attempt.handoff.id` of the picker being answered. When sent and it is not the consent this card is waiting on, the answer is 409 `handoff_mismatch` — a stale tab cannot finalize a newer consent’s choice. Omitted: the current picker is finalized."
          }
        },
        "required": [
          "external_id"
        ]
      },
      "ProviderSyncResult": {
        "type": "object",
        "properties": {
          "enqueued": {
            "type": "boolean",
            "enum": [
              true
            ]
          }
        },
        "required": [
          "enqueued"
        ]
      },
      "ProviderSyncBody": {
        "type": "object",
        "properties": {
          "force": {
            "type": "boolean",
            "description": "Switch off products the store no longer lists even when it looks like a mass drop (the drop guard). Only for a merchant who really emptied the store."
          }
        }
      },
      "CrmStage": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "description": "The CRM’s own stage id — the `{stageId}` a write names."
          },
          "name": {
            "type": "string",
            "description": "The stage’s name in the CRM, verbatim."
          },
          "position": {
            "type": "number",
            "description": "Its position in the pipeline (CRM order)."
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "description": "The outcome in force (an `outcomes[].key`; `ignored` = «No contar»), or `null` when the stage counts as nothing yet."
          },
          "outcome_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "mark": {
            "type": "string",
            "enum": [
              "suggested",
              "changed",
              "default",
              "unset"
            ],
            "description": "`suggested` — on the suggestion (applied automatically, or chosen to match it); `changed` — someone chose something else; `default` — a value nobody chose (an older default), worth a look; `unset` — not assigned, counts as nothing."
          },
          "suggested": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "outcome": {
                "type": "string",
                "description": "An `outcomes[].key`."
              },
              "label": {
                "type": "string",
                "description": "Its ready Spanish name («Cita agendada»)."
              }
            },
            "required": [
              "outcome",
              "label"
            ],
            "description": "What the stage’s name points to. Deterministic: the same name always gets the same suggestion. `null` when the name points to nothing — `no_suggestion_label` says so."
          },
          "no_suggestion_label": {
            "type": [
              "string",
              "null"
            ]
          },
          "needs_choice": {
            "type": "boolean",
            "description": "No suggestion and nobody decided: a person has to pick (the sheet’s banner counts these). The auto-apply never guesses for them."
          },
          "writable": {
            "type": "boolean"
          },
          "not_writable_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it cannot be edited now, as a sentence."
          }
        },
        "required": [
          "external_id",
          "name",
          "position",
          "outcome",
          "outcome_label",
          "mark",
          "suggested",
          "no_suggestion_label",
          "needs_choice",
          "writable",
          "not_writable_label"
        ]
      },
      "CrmStageMapping": {
        "type": "object",
        "properties": {
          "provider": {
            "type": "string",
            "enum": [
              "gohighlevel"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ready",
              "awaiting_sync",
              "reconnect_required",
              "not_connected"
            ],
            "description": "`ready` — every stage is listed. `awaiting_sync` — connected, the pipelines are still being read. `reconnect_required` — the stages stay listed and editable, but the connection needs re-authorizing. `not_connected` — nothing to list."
          },
          "status_label": {
            "type": [
              "string",
              "null"
            ],
            "description": "A ready sentence for any status but `ready`."
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the pipelines were last read from the CRM."
          },
          "syncing": {
            "type": "boolean",
            "description": "A pipelines read was asked for in the last few minutes."
          },
          "can_write": {
            "type": "boolean"
          },
          "write_blocked": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "addon_inactive",
                  "access_pending",
                  "measurement_inactive"
                ]
              },
              "label": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "label"
            ],
            "description": "Why the mapping cannot be edited now: `addon_inactive` (the Vitrina Ads add-on is off), `access_pending` (edit access is still being enabled — try again in a few minutes), `measurement_inactive` (outcome measurement is not enabled on the workspace)."
          },
          "next_step": {
            "type": "object",
            "properties": {
              "action": {
                "type": "string",
                "enum": [
                  "sync",
                  "reconnect",
                  "connect",
                  "none"
                ],
                "description": "`sync` — `POST …/stages/sync`; `reconnect` / `connect` — the connection card’s own `…/connect`; `none`."
              },
              "label": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "action",
              "label"
            ]
          },
          "outcomes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "label"
              ]
            },
            "description": "Every outcome a stage can map to, in picker order, ending in `ignored` («No contar»)."
          },
          "pipelines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "external_id": {
                  "type": "string",
                  "description": "The CRM’s own pipeline id."
                },
                "name": {
                  "type": "string"
                },
                "account_name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The CRM sub-account — only when several are connected."
                },
                "stages": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CrmStage"
                  }
                }
              },
              "required": [
                "external_id",
                "name",
                "account_name",
                "stages"
              ]
            },
            "description": "Pipelines in CRM order, each with its stages in CRM order."
          },
          "summary": {
            "type": "object",
            "properties": {
              "pipelines": {
                "type": "number"
              },
              "stages": {
                "type": "number"
              },
              "suggested": {
                "type": "number"
              },
              "changed": {
                "type": "number"
              },
              "needs_choice": {
                "type": "number"
              },
              "awaiting_sync": {
                "type": "number"
              }
            },
            "required": [
              "pipelines",
              "stages",
              "suggested",
              "changed",
              "needs_choice",
              "awaiting_sync"
            ]
          }
        },
        "required": [
          "provider",
          "status",
          "status_label",
          "synced_at",
          "syncing",
          "can_write",
          "write_blocked",
          "next_step",
          "outcomes",
          "pipelines",
          "summary"
        ]
      },
      "CrmStageOutcomeBody": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "minLength": 1,
            "maxLength": 64,
            "description": "An `outcomes[].key` from the stages read — `ignored` for «No contar». Anything else is 422 `invalid_outcome`."
          }
        },
        "required": [
          "outcome"
        ]
      },
      "AdsCoverage": {
        "type": "object",
        "properties": {
          "measured_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The workspace’s measurement start (America/Santiago day): the earliest ad touch Vitrina holds (an imported historical touch or a conversation an ad opened) or the first day the attribution engine credited an outcome to an ad. `null` = unknown (nothing clipped)."
          },
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The day the response’s figures are counted from: the later of the requested `from` and `measured_from`."
          },
          "clipped": {
            "type": "boolean",
            "description": "`true` when measurement started inside the requested period: say «Medimos desde {measured_from}» next to the figures."
          },
          "previous_measured": {
            "type": "boolean",
            "description": "`false` when the comparison period was not measured from its first day: show «—» for every comparison, never a delta against it."
          },
          "touches_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The earliest ad touch Vitrina holds (an imported history, a conversation an ad opened). Bounds the people views («antes de medir»), never a ratio."
          },
          "conversations": {
            "type": "object",
            "properties": {
              "measured_from": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "clipped": {
                "type": "boolean"
              },
              "previous_measured": {
                "type": "boolean"
              },
              "ad_origin_gap": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "description": "Days inside the period no ad-origin record covers (between an imported history’s last ad touch and Vitrina’s first ad-opened conversation): their conversations are not attributable to an ad. Name them next to the figures; never estimate them. `null` = none."
              },
              "stages_from": {
                "type": "object",
                "properties": {
                  "appointments": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "closes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "appointments",
                  "closes"
                ],
                "description": "The day each STAGE figure of the conversation funnel starts when it is later than the conversations’ own start (an imported history reaches back before Vitrina recorded citas and accepted presupuestos). Non-null = say «citas desde el {day}» / «presupuestos aceptados desde el {day}» next to that figure; `null` = it covers the whole conversation window."
              }
            },
            "required": [
              "measured_from",
              "from",
              "clipped",
              "previous_measured"
            ],
            "description": "CONVERSATION-family coverage: conversations and cost per conversation count from the first ad-opened conversation or lead (`measured_from`) — or, when the workspace imported a conversation history with its ad touches, from the start of that history’s complete stretch (as `people`), never from the money start above. Say «Medimos desde» with this date next to those figures, and show «—» for their comparisons when `previous_measured` is false."
          },
          "people": {
            "type": "object",
            "properties": {
              "measured_from": {
                "type": [
                  "string",
                  "null"
                ],
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "clipped": {
                "type": "boolean"
              },
              "previous_measured": {
                "type": "boolean"
              },
              "ad_origin_gap": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "description": "Days inside the period no ad-origin record covers — between the end of an imported history and Vitrina’s first ad-opened conversation. Name them next to the people («sin origen de anuncio el 19 sep»); never estimate them. `null` = none."
              }
            },
            "required": [
              "measured_from",
              "from",
              "clipped",
              "previous_measured",
              "ad_origin_gap"
            ],
            "description": "PEOPLE-family coverage: «personas que escribieron» and «desde anuncios» count from the start of the workspace’s COMPLETE history (`measured_from`): an imported history on its real message times once its census shows it complete, else the conversation start. When `clipped`, the people are a different window from the money: give them their own line naming it, never beside the period’s money. Absent = use `conversations`."
          },
          "payments_connected": {
            "type": "boolean",
            "description": "Whether payments reach Vitrina at all (a clinic-software mirror, the money module, or any payment recorded). `false` = every payment-based return (total return, first payment, «$ pagados») is a MISSING measurement: show «—» and say what is needed (connect the clinic’s payments), never «0×» or «$0»."
          },
          "ad_payments_recorded": {
            "type": "boolean",
            "description": "Whether anybody an ad brought has EVER paid (a payment counted toward ad return, any day). Present only when the payments rail is Vitrina’s own ledger or a Reservo sales mirror (never a Dentalink / Medilink mirror). `false` with `payments_connected: true` = payments reach Vitrina but no person from an ad has paid yet: an empty state that says so («Aún ningún paciente de anuncios ha pagado»), never «$0 pagados» or «0×». Absent = as before."
          },
          "accepted_recorded": {
            "type": "boolean",
            "description": "Whether the workspace has ever recorded an accepted quote / closed sale. `false` = the accepted-quotes return is a MISSING measurement: «—», never «0×» or «$0»."
          },
          "start_unknown": {
            "type": "boolean",
            "description": "`true` when the measurement start could not be read: every ratio (return, primer pago, MER, accepted plans) is unknown — show «—», never a return over days nobody measured."
          }
        },
        "required": [
          "measured_from",
          "from",
          "clipped",
          "previous_measured"
        ],
        "description": "The coverage window (see `AdsCoverage`); absent on the sample."
      },
      "AdsPatientReturn": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "patients": {
            "type": "integer",
            "minimum": 0,
            "description": "Ad people with at least one counted payment in the period."
          },
          "payments": {
            "type": "integer",
            "minimum": 0
          },
          "value": {
            "type": "number",
            "minimum": 0,
            "description": "Their payments in the period (CLP): first + later."
          },
          "first_value": {
            "type": "number",
            "minimum": 0,
            "description": "Of `value`, each person’s first payment."
          },
          "later_value": {
            "type": "number",
            "minimum": 0,
            "description": "Of `value`, the later payments (cuotas, more treatment)."
          },
          "arrived_value": {
            "type": "number",
            "minimum": 0,
            "description": "Of `value`, the payments of people whose FIRST message came from an ad («llegaron por un anuncio»). `arrived_value + returned_value = value`. Absent on the sample."
          },
          "returned_value": {
            "type": "number",
            "minimum": 0,
            "description": "Of `value`, the payments counted through a LATER conversation an ad opened (re-engagement, retargeting — «volvieron por un anuncio»). Absent on the sample."
          },
          "arrived_patients": {
            "type": "integer",
            "minimum": 0,
            "description": "Of `patients`, the people counted as «llegaron»; `arrived_patients + returned_patients = patients`."
          },
          "returned_patients": {
            "type": "integer",
            "minimum": 0,
            "description": "Of `patients`, the people counted as «volvieron»."
          },
          "roas": {
            "type": [
              "number",
              "null"
            ],
            "description": "`value ÷ spend`; `null` when spend is unknown or 0."
          }
        },
        "required": [
          "patients",
          "payments",
          "value",
          "first_value",
          "later_value",
          "roas"
        ],
        "description": "«Retorno»: the payments of the period counted toward ad return whose counting ad (the ad-opened conversation within the 12 months before the payment) is this ad or one of this campaign’s ads. Not set on ad sets. `null` = unknown."
      },
      "AdsReportRow": {
        "type": "object",
        "properties": {
          "spend": {
            "type": [
              "number",
              "null"
            ]
          },
          "funnel": {
            "type": "object",
            "properties": {
              "conversations": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "replied": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "appointments": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "attended": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "closes": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              }
            },
            "required": [
              "conversations",
              "replied",
              "appointments",
              "attended",
              "closes"
            ],
            "description": "Vitrina’s own funnel: conversations each ad OPENED in the period and the stages recorded against them (a campaign / ad set sums its ads). `null` = unknown (the read failed), never 0."
          },
          "cost_per": {
            "type": "object",
            "properties": {
              "conversation": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "appointment": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "attended": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "close": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "conversation",
              "appointment",
              "attended",
              "close"
            ]
          },
          "cash": {
            "type": [
              "number",
              "null"
            ],
            "description": "Payments credited to the entity (CLP). Cash only."
          },
          "return": {
            "type": [
              "number",
              "null"
            ],
            "description": "cash ÷ spend. `null` when no payment was recorded in the period (see `cash_recorded`) — the return is unknown then, never 0×."
          },
          "accepted_value": {
            "type": [
              "number",
              "null"
            ],
            "description": "The recorded value of the accepted presupuestos / closed sales of the conversations the entity opened (CLP) — pipeline, never cash. `null` = unknown."
          },
          "accepted_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums."
          },
          "diagnosed": {
            "type": [
              "integer",
              "null"
            ],
            "description": "D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown."
          },
          "accepted_return": {
            "type": [
              "number",
              "null"
            ],
            "description": "`accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend."
          },
          "patient_return": {
            "$ref": "#/components/schemas/AdsPatientReturn"
          },
          "meta": {
            "type": "object",
            "properties": {
              "conversations_started": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "cost_per_conversation": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "conversations_started",
              "cost_per_conversation"
            ],
            "description": "Meta’s OWN figures (its 7-day window, every sender) — never added to Vitrina’s."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Platform id; `null` for the unmapped bucket."
          },
          "level": {
            "type": "string",
            "enum": [
              "campaign",
              "ad_set",
              "ad"
            ]
          },
          "name": {
            "type": "string"
          },
          "parent": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "external_id",
              "name"
            ],
            "description": "An ad’s ad set / an ad set’s campaign; `null` for a campaign."
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "spark": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "number"
            },
            "description": "Daily spend, oldest first; `null` unread."
          },
          "unmapped": {
            "type": "boolean",
            "enum": [
              true
            ],
            "description": "Ads that opened conversations but have no row at this level yet."
          }
        },
        "required": [
          "spend",
          "funnel",
          "cost_per",
          "cash",
          "return",
          "meta",
          "external_id",
          "level",
          "name",
          "parent",
          "thumbnail_url",
          "spark"
        ]
      },
      "AdsReport": {
        "type": "object",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "campaign",
              "ad_set",
              "ad"
            ]
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ],
            "description": "The days the rows were read over: `from` is the later of the requested `from` and the measurement start (`coverage`)."
          },
          "coverage": {
            "$ref": "#/components/schemas/AdsCoverage"
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsReportRow"
            }
          },
          "tail": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "website",
                    "portals",
                    "direct"
                  ]
                },
                "conversations": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "required": [
                "key",
                "conversations"
              ]
            },
            "description": "«Sin anuncio»: the period’s conversations with no ad behind them. `null` = unknown."
          },
          "totals": {
            "type": "object",
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "funnel": {
                "type": "object",
                "properties": {
                  "conversations": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "replied": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "appointments": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "attended": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "closes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                },
                "required": [
                  "conversations",
                  "replied",
                  "appointments",
                  "attended",
                  "closes"
                ],
                "description": "Vitrina’s own funnel: conversations each ad OPENED in the period and the stages recorded against them (a campaign / ad set sums its ads). `null` = unknown (the read failed), never 0."
              },
              "cost_per": {
                "type": "object",
                "properties": {
                  "conversation": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "appointment": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "attended": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "close": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "conversation",
                  "appointment",
                  "attended",
                  "close"
                ]
              },
              "cash": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Payments credited to the entity (CLP). Cash only."
              },
              "return": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "cash ÷ spend. `null` when no payment was recorded in the period (see `cash_recorded`) — the return is unknown then, never 0×."
              },
              "accepted_value": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The recorded value of the accepted presupuestos / closed sales of the conversations the entity opened (CLP) — pipeline, never cash. `null` = unknown."
              },
              "accepted_count": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums."
              },
              "diagnosed": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown."
              },
              "accepted_return": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "`accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend."
              },
              "patient_return": {
                "$ref": "#/components/schemas/AdsPatientReturn"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "conversations_started": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "cost_per_conversation": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "conversations_started",
                  "cost_per_conversation"
                ],
                "description": "Meta’s OWN figures (its 7-day window, every sender) — never added to Vitrina’s."
              }
            },
            "required": [
              "spend",
              "funnel",
              "cost_per",
              "cash",
              "return",
              "meta"
            ]
          },
          "pipeline": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "closed_count": {
                "type": "integer"
              },
              "closed_value": {
                "type": "number"
              }
            },
            "required": [
              "closed_count",
              "closed_value"
            ],
            "description": "Closes credited to an ad and their recorded value — PIPELINE, never in `return`."
          },
          "cash_recorded": {
            "type": "boolean"
          },
          "reconciliation": {
            "type": "object",
            "properties": {
              "overview_spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "spend_matches": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "conversations_total": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "conversations_from_ads": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              },
              "conversations_without_ad": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0
              }
            },
            "required": [
              "overview_spend",
              "spend_matches",
              "conversations_total",
              "conversations_from_ads",
              "conversations_without_ad"
            ]
          }
        },
        "required": [
          "level",
          "window",
          "rows",
          "tail",
          "totals",
          "pipeline",
          "cash_recorded",
          "reconciliation"
        ]
      },
      "AdsAdSheet": {
        "type": "object",
        "properties": {
          "ad": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "ad_set": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "external_id",
                  "name"
                ]
              },
              "campaign": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "external_id"
                ]
              },
              "thumbnail_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "body": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "has_video": {
                "type": "boolean"
              },
              "video_external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Meta’s id of the ad’s video; `null` for an image ad. Play it with `GET /ads/ads/{id}/video` (the source is short-lived: ask on play)."
              },
              "ads_manager_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The ad in Meta Ads Manager (opens in a new tab); `null` when the ad account is unknown."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "impressions": {
                "type": "number"
              },
              "clicks": {
                "type": "number"
              },
              "ctr": {
                "type": "number"
              },
              "cpm": {
                "type": "number"
              },
              "cpc": {
                "type": "number"
              },
              "cash_count": {
                "type": "number"
              },
              "cash_value": {
                "type": "number"
              },
              "meta_conversations_started": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "external_id",
              "name",
              "ad_set",
              "campaign",
              "thumbnail_url",
              "title",
              "body",
              "has_video",
              "video_external_id",
              "ads_manager_url",
              "spend",
              "impressions",
              "clicks",
              "ctr",
              "cpm",
              "cpc",
              "cash_count",
              "cash_value",
              "meta_conversations_started"
            ],
            "description": "The creative and the delivery metrics; `null` when the engine read failed."
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "row": {
            "type": "object",
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "funnel": {
                "type": "object",
                "properties": {
                  "conversations": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "replied": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "appointments": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "attended": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "closes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  }
                },
                "required": [
                  "conversations",
                  "replied",
                  "appointments",
                  "attended",
                  "closes"
                ],
                "description": "Vitrina’s own funnel: conversations each ad OPENED in the period and the stages recorded against them (a campaign / ad set sums its ads). `null` = unknown (the read failed), never 0."
              },
              "cost_per": {
                "type": "object",
                "properties": {
                  "conversation": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "appointment": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "attended": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "close": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "conversation",
                  "appointment",
                  "attended",
                  "close"
                ]
              },
              "cash": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Payments credited to the entity (CLP). Cash only."
              },
              "return": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "cash ÷ spend. `null` when no payment was recorded in the period (see `cash_recorded`) — the return is unknown then, never 0×."
              },
              "accepted_value": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The recorded value of the accepted presupuestos / closed sales of the conversations the entity opened (CLP) — pipeline, never cash. `null` = unknown."
              },
              "accepted_count": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "How many accepted presupuestos / closed sales `accepted_value` is. Totals and campaign rows: the engine-credited list Resumen sums."
              },
              "diagnosed": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "D60 · «Con diagnóstico»: people whose conversation the entity opened and who got a presupuesto presented. Absent = unknown."
              },
              "accepted_return": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "`accepted_value` ÷ spend («Retorno en presupuestos»). `null` = unknown or no spend."
              },
              "patient_return": {
                "$ref": "#/components/schemas/AdsPatientReturn"
              },
              "meta": {
                "type": "object",
                "properties": {
                  "conversations_started": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "cost_per_conversation": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "conversations_started",
                  "cost_per_conversation"
                ],
                "description": "Meta’s OWN figures (its 7-day window, every sender) — never added to Vitrina’s."
              }
            },
            "required": [
              "spend",
              "funnel",
              "cost_per",
              "cash",
              "return",
              "meta"
            ],
            "description": "The same figures as the Reporte row for this ad."
          },
          "cash_recorded": {
            "type": "boolean"
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "conversation_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "contact_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "label": {
                  "type": "string",
                  "description": "Initials, or the name where allowed. Never a phone or an e-mail."
                },
                "masked": {
                  "type": "boolean"
                },
                "stage": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "stage_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "opened_at": {
                  "type": "string"
                },
                "value": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              },
              "required": [
                "conversation_id",
                "contact_id",
                "label",
                "masked",
                "stage",
                "stage_at",
                "opened_at",
                "value"
              ]
            },
            "description": "The conversations this ad opened, newest first. Healthcare: always initials. Elsewhere a name only with `contacts:read`. No export."
          },
          "people_more": {
            "type": "boolean"
          },
          "people_hidden": {
            "type": "boolean",
            "description": "The caller lacks `contacts:read`: `people` is empty on purpose."
          }
        },
        "required": [
          "ad",
          "window",
          "row",
          "cash_recorded",
          "people",
          "people_more",
          "people_hidden"
        ]
      },
      "AdsAdVideo": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "source",
              "embed"
            ],
            "description": "`source`: a direct URL a `<video>` plays. `embed`: the Facebook embed iframe (common when the Page is not connected) — never an error."
          },
          "source": {
            "type": [
              "string",
              "null"
            ]
          },
          "embed_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "watch_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The public Facebook watch page: the fallback link."
          }
        },
        "required": [
          "type",
          "source",
          "embed_url",
          "watch_url"
        ]
      },
      "AdsDemoFunnelRow": {
        "type": "object",
        "properties": {
          "arm": {
            "type": "string",
            "enum": [
              "whatsapp",
              "instant_form",
              "website",
              "other",
              "unassigned"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta id at this level; `null` on an arm row and on the «sin atribución» row."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "external_id",
              "name"
            ]
          },
          "unattributed": {
            "type": "boolean",
            "description": "The arm’s people with no id at this level."
          },
          "leads": {
            "type": "integer",
            "minimum": 0,
            "description": "People with a lead (`lead_created`)."
          },
          "qualified": {
            "type": "integer",
            "minimum": 0,
            "description": "Of those, ICP-qualified (`lead_qualified`)."
          },
          "booked": {
            "type": "integer",
            "minimum": 0,
            "description": "People who booked: a real cita in the agenda (Vitrina-booked or mirrored from the clinic software) since they arrived, or a booking / held event in the ledger. In basis `cohort` held ⊆ booked."
          },
          "held": {
            "type": "integer",
            "minimum": 0,
            "description": "People whose demo was held (`appointment_attended`)."
          },
          "paid": {
            "type": "integer",
            "minimum": 0,
            "description": "People with a first paid invoice (`first_payment`)."
          },
          "booked_held": {
            "type": "integer",
            "minimum": 0,
            "description": "People with BOTH a booking and a held appointment: the show-rate numerator, so the rate never passes 100 %."
          },
          "agreed_without_cita": {
            "type": "integer",
            "minimum": 0,
            "description": "People NOT booked whose lead stood on a stage that says an hour was agreed («Hora acordada, sin cita en la agenda»): a leak, never a booking."
          },
          "contacted_24h": {
            "type": "integer",
            "minimum": 0,
            "description": "Leads that got a reply (agent, human or API) within 24 h of the lead."
          },
          "spend": {
            "type": [
              "number",
              "null"
            ],
            "description": "`null` = unknown or none on this row."
          },
          "costs": {
            "type": "object",
            "properties": {
              "per_lead": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "per_qualified": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "per_booked": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "per_held": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "THE metric the A/B is judged on (D5)."
              },
              "per_paid": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "per_lead",
              "per_qualified",
              "per_booked",
              "per_held",
              "per_paid"
            ]
          },
          "rates": {
            "type": "object",
            "properties": {
              "qualified_share": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "qualified ÷ leads"
              },
              "contacted_24h_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "contacted within 24 h ÷ leads"
              },
              "lead_to_booked": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "booked ÷ leads"
              },
              "show_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "booked_held ÷ booked (never above 1)"
              }
            },
            "required": [
              "qualified_share",
              "contacted_24h_rate",
              "lead_to_booked",
              "show_rate"
            ]
          }
        },
        "required": [
          "arm",
          "external_id",
          "name",
          "parent",
          "unattributed",
          "leads",
          "qualified",
          "booked",
          "held",
          "paid",
          "booked_held",
          "agreed_without_cita",
          "contacted_24h",
          "spend",
          "costs",
          "rates"
        ]
      },
      "AdsDemoFunnel": {
        "type": "object",
        "properties": {
          "level": {
            "type": "string",
            "enum": [
              "arm",
              "campaign",
              "ad_set",
              "ad"
            ]
          },
          "basis": {
            "type": "string",
            "enum": [
              "cohort",
              "event"
            ]
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsDemoFunnelRow"
            }
          },
          "arms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsDemoFunnelRow"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "leads": {
                "type": "integer",
                "minimum": 0,
                "description": "People with a lead (`lead_created`)."
              },
              "qualified": {
                "type": "integer",
                "minimum": 0,
                "description": "Of those, ICP-qualified (`lead_qualified`)."
              },
              "booked": {
                "type": "integer",
                "minimum": 0,
                "description": "People who booked: a real cita in the agenda (Vitrina-booked or mirrored from the clinic software) since they arrived, or a booking / held event in the ledger. In basis `cohort` held ⊆ booked."
              },
              "held": {
                "type": "integer",
                "minimum": 0,
                "description": "People whose demo was held (`appointment_attended`)."
              },
              "paid": {
                "type": "integer",
                "minimum": 0,
                "description": "People with a first paid invoice (`first_payment`)."
              },
              "booked_held": {
                "type": "integer",
                "minimum": 0,
                "description": "People with BOTH a booking and a held appointment: the show-rate numerator, so the rate never passes 100 %."
              },
              "agreed_without_cita": {
                "type": "integer",
                "minimum": 0,
                "description": "People NOT booked whose lead stood on a stage that says an hour was agreed («Hora acordada, sin cita en la agenda»): a leak, never a booking."
              },
              "contacted_24h": {
                "type": "integer",
                "minimum": 0,
                "description": "Leads that got a reply (agent, human or API) within 24 h of the lead."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "costs": {
                "type": "object",
                "properties": {
                  "per_lead": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_qualified": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_booked": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_held": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "THE metric the A/B is judged on (D5)."
                  },
                  "per_paid": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "per_lead",
                  "per_qualified",
                  "per_booked",
                  "per_held",
                  "per_paid"
                ]
              },
              "rates": {
                "type": "object",
                "properties": {
                  "qualified_share": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "qualified ÷ leads"
                  },
                  "contacted_24h_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "contacted within 24 h ÷ leads"
                  },
                  "lead_to_booked": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "booked ÷ leads"
                  },
                  "show_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "booked_held ÷ booked (never above 1)"
                  }
                },
                "required": [
                  "qualified_share",
                  "contacted_24h_rate",
                  "lead_to_booked",
                  "show_rate"
                ]
              }
            },
            "required": [
              "leads",
              "qualified",
              "booked",
              "held",
              "paid",
              "booked_held",
              "agreed_without_cita",
              "contacted_24h",
              "spend",
              "costs",
              "rates"
            ],
            "description": "Every person in the window, ad or not; Σ rows = totals."
          },
          "ad_totals": {
            "type": "object",
            "properties": {
              "leads": {
                "type": "integer",
                "minimum": 0,
                "description": "People with a lead (`lead_created`)."
              },
              "qualified": {
                "type": "integer",
                "minimum": 0,
                "description": "Of those, ICP-qualified (`lead_qualified`)."
              },
              "booked": {
                "type": "integer",
                "minimum": 0,
                "description": "People who booked: a real cita in the agenda (Vitrina-booked or mirrored from the clinic software) since they arrived, or a booking / held event in the ledger. In basis `cohort` held ⊆ booked."
              },
              "held": {
                "type": "integer",
                "minimum": 0,
                "description": "People whose demo was held (`appointment_attended`)."
              },
              "paid": {
                "type": "integer",
                "minimum": 0,
                "description": "People with a first paid invoice (`first_payment`)."
              },
              "booked_held": {
                "type": "integer",
                "minimum": 0,
                "description": "People with BOTH a booking and a held appointment: the show-rate numerator, so the rate never passes 100 %."
              },
              "agreed_without_cita": {
                "type": "integer",
                "minimum": 0,
                "description": "People NOT booked whose lead stood on a stage that says an hour was agreed («Hora acordada, sin cita en la agenda»): a leak, never a booking."
              },
              "contacted_24h": {
                "type": "integer",
                "minimum": 0,
                "description": "Leads that got a reply (agent, human or API) within 24 h of the lead."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "costs": {
                "type": "object",
                "properties": {
                  "per_lead": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_qualified": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_booked": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "per_held": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "THE metric the A/B is judged on (D5)."
                  },
                  "per_paid": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "per_lead",
                  "per_qualified",
                  "per_booked",
                  "per_held",
                  "per_paid"
                ]
              },
              "rates": {
                "type": "object",
                "properties": {
                  "qualified_share": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "qualified ÷ leads"
                  },
                  "contacted_24h_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "contacted within 24 h ÷ leads"
                  },
                  "lead_to_booked": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "booked ÷ leads"
                  },
                  "show_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "booked_held ÷ booked (never above 1)"
                  }
                },
                "required": [
                  "qualified_share",
                  "contacted_24h_rate",
                  "lead_to_booked",
                  "show_rate"
                ]
              }
            },
            "required": [
              "leads",
              "qualified",
              "booked",
              "held",
              "paid",
              "booked_held",
              "agreed_without_cita",
              "contacted_24h",
              "spend",
              "costs",
              "rates"
            ],
            "description": "The paid funnel: all ad spend over the people an ad brought (every arm but `other`). Its costs are the ones to quote as «cost per …»."
          },
          "measured_since": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Set when the outcome ledger starts after the requested `from`: `window.from` was moved to this day, spend included."
          },
          "steps": {
            "type": "object",
            "properties": {
              "qualified": {
                "type": "boolean",
                "description": "False when the tenant never recorded `lead_qualified` (no qualifying step, e.g. a clinic): the column is structurally 0."
              }
            },
            "required": [
              "qualified"
            ],
            "description": "Which funnel steps this tenant has."
          },
          "reconciliation": {
            "type": "object",
            "properties": {
              "overview_spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "spend_matches": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "events": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "lead_created": {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "events",
                      "people"
                    ]
                  },
                  "lead_qualified": {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "events",
                      "people"
                    ]
                  },
                  "appointment_booked": {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "events",
                      "people"
                    ]
                  },
                  "appointment_attended": {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "events",
                      "people"
                    ]
                  },
                  "first_payment": {
                    "type": "object",
                    "properties": {
                      "events": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "events",
                      "people"
                    ]
                  }
                },
                "required": [
                  "lead_created",
                  "lead_qualified",
                  "appointment_booked",
                  "appointment_attended",
                  "first_payment"
                ]
              },
              "counts_match": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "leads_without_ad": {
                "type": "integer",
                "minimum": 0
              }
            },
            "required": [
              "overview_spend",
              "spend_matches",
              "events",
              "counts_match",
              "leads_without_ad"
            ]
          }
        },
        "required": [
          "level",
          "basis",
          "window",
          "rows",
          "arms",
          "totals",
          "ad_totals",
          "measured_since",
          "steps",
          "reconciliation"
        ]
      },
      "AdsPatientReturnTotal": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdsPatientReturn"
          },
          {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "median_days_to_first_payment": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Median days from the ad touch that counts it to each person’s first payment, over the first payments of the period."
              },
              "avg_paid_per_patient": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Everything the ad patients who paid have paid so far (to the period’s end) ÷ how many they are (CLP)."
              }
            },
            "required": [
              "median_days_to_first_payment",
              "avg_paid_per_patient"
            ]
          }
        ],
        "description": "«Retorno total de pacientes de anuncios (primer pago + cuotas)» over the money-covered window. On `current` only; `null` = unknown (the credit is still warming)."
      },
      "AdsOverviewDay": {
        "type": "object",
        "properties": {
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "spend": {
            "type": [
              "number",
              "null"
            ],
            "description": "`null` exactly when the engine could not scope spend."
          },
          "revenue": {
            "type": "number",
            "description": "Attributed value that day."
          },
          "outcomes": {
            "type": "integer"
          },
          "attributed_outcomes": {
            "type": "integer"
          },
          "partial": {
            "type": "boolean",
            "description": "`true` on the day still running (today, America/Santiago): end the solid line before it."
          },
          "patient_first_value": {
            "type": [
              "number",
              "null"
            ],
            "description": "What ad patients paid that day as their FIRST payment (CLP) — the same payments as `patient_return.first_value`, each on the day it was received. `null` = unknown (the patient total is not known); absent = an older server."
          },
          "patient_later_value": {
            "type": [
              "number",
              "null"
            ],
            "description": "What ad patients paid that day in later payments (cuotas, further treatments). `patient_first_value + patient_later_value` over the window adds up to `patient_return.value` to the peso."
          }
        },
        "required": [
          "date",
          "spend",
          "revenue",
          "outcomes",
          "attributed_outcomes",
          "partial"
        ]
      },
      "AdsGap": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "First day with missing data, YYYY-MM-DD."
          },
          "to": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Last day with missing data, YYYY-MM-DD; `null` = still ongoing."
          },
          "cause": {
            "type": "string",
            "enum": [
              "meta_disconnected",
              "meta_stale",
              "tag_silent",
              "capi_failing",
              "whatsapp_disconnected",
              "engine_facts_missing"
            ],
            "description": "What broke: `meta_disconnected` / `meta_stale` (the ad account read), `tag_silent` (the site tag stopped reporting), `capi_failing` (the Conversions API sends), `whatsapp_disconnected`, `engine_facts_missing` (clinic payments/plans not reaching the attribution)."
          },
          "metrics": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spend",
                "conversations",
                "outcomes"
              ]
            },
            "description": "The figures UNKNOWN inside the gap: draw them as missing (a break in the line, «sin datos»), never as 0. Empty = nothing Vitrina shows is blanked; the window is annotated only."
          },
          "incident_id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "from",
          "to",
          "cause",
          "metrics",
          "incident_id"
        ]
      },
      "AdsMetaSendInterval": {
        "type": "object",
        "properties": {
          "started_at": {
            "type": "string",
            "format": "date-time",
            "description": "When outcomes started shipping to Meta (UTC)."
          },
          "stopped_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When they stopped; `null` = still sending."
          }
        },
        "required": [
          "started_at",
          "stopped_at"
        ]
      },
      "AdsMetaSendMarker": {
        "type": "object",
        "properties": {
          "first_started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "The first time the workspace started sending its outcomes to Meta («Empezar a enviar»): the experiment marker every Ads chart draws as «Empezaste a enviar a Meta · {fecha}». Before it, Meta optimised with no events from Vitrina. `null` = never sent."
          },
          "sending": {
            "type": "boolean",
            "description": "Outcomes ship to Meta right now (an interval is open)."
          },
          "intervals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsMetaSendInterval"
            },
            "description": "Every interval in which outcomes shipped to Meta, oldest first — a before/after comparison must leave out the days between them."
          }
        },
        "required": [
          "first_started_at",
          "sending",
          "intervals"
        ],
        "description": "When the workspace started (and stopped) sending its outcomes to Meta — draw `first_started_at` as a vertical marker when it falls in the window. A fact about the workspace: the sample carries the real one."
      },
      "AdsConversionSourceCrm": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "vitrina",
              "gohighlevel",
              "mercadopago"
            ],
            "description": "Where bookings and sales come from: the connected provider while its connection is live, otherwise `vitrina` (Vitrina’s own records)."
          },
          "display_name": {
            "type": "string",
            "description": "Ready to render: «Vitrina», «GoHighLevel», «Mercado Pago»."
          },
          "connection_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "connected",
              "needs_reconnect"
            ],
            "description": "The provider connection’s state (`needs_reconnect`: still the source, but its data stops until it is reconnected); `null` when the source is Vitrina."
          }
        },
        "required": [
          "source",
          "display_name",
          "connection_state"
        ]
      },
      "AdsConversionSourcePayments": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "vitrina",
              "gohighlevel",
              "mercadopago"
            ],
            "description": "Where payments come from: the connected provider while its connection is live, otherwise `vitrina` (Vitrina’s own records)."
          },
          "display_name": {
            "type": "string",
            "description": "Ready to render: «Vitrina», «GoHighLevel», «Mercado Pago»."
          },
          "connection_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "connected",
              "needs_reconnect"
            ],
            "description": "The provider connection’s state (`needs_reconnect`: still the source, but its data stops until it is reconnected); `null` when the source is Vitrina."
          }
        },
        "required": [
          "source",
          "display_name",
          "connection_state"
        ]
      },
      "AdsOverview": {
        "type": "object",
        "properties": {
          "current": {
            "type": "object",
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "spend_available": {
                "type": "boolean"
              },
              "clicks": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "revenue": {
                "type": "number"
              },
              "organic_direct_revenue": {
                "type": "number"
              },
              "roas": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "outcomes": {
                "type": "integer"
              },
              "attributed_outcomes": {
                "type": "integer"
              },
              "attributed_cash_outcomes": {
                "type": "integer"
              },
              "coverage_percent": {
                "type": "number"
              },
              "cash_revenue": {
                "type": "number",
                "description": "«Todo lo cobrado»: every payment received in the period (CLP), ad or not. See `cash_source`."
              },
              "cash_payments": {
                "type": "integer"
              },
              "cash_source": {
                "type": "string",
                "enum": [
                  "clinic_ledger",
                  "engine"
                ],
                "description": "Where `cash_revenue` / `cash_payments` come from: `clinic_ledger` = the clinic software (Dentalink / Medilink), the ONLY payment source while it is connected — every payment, linked to a contact or not; `engine` = the attribution total (no clinic software). Absent on the sample."
              },
              "first_payment_revenue": {
                "type": "number"
              },
              "first_payment_customers": {
                "type": "integer",
                "description": "The engine’s distinct NEW paying customers in the window, ad or not (the CAC denominator). It is NOT «patients who paid from ads»: that count is `patient_return.patients` (the Resumen hero, the campaigns totals and the Monday brief all read it)."
              },
              "recurring_revenue": {
                "type": "number"
              },
              "recurring_customers": {
                "type": "integer"
              },
              "refunds": {
                "type": "integer"
              },
              "refund_amount": {
                "type": "number"
              },
              "disputes": {
                "type": "integer"
              },
              "dispute_amount": {
                "type": "number"
              },
              "dispute_reversed_amount": {
                "type": "number"
              },
              "outcome_counts": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "attributed_pipeline": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "closed_count": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "closed_value": {
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": [
                  "closed_count",
                  "closed_value"
                ],
                "description": "PIPELINE, never cash (not in `revenue` / `roas`): the period’s closes (`closed_won` — a clinic’s accepted plans, a dealer’s closed sales) that the attribution engine credited to an ad, with the value Vitrina recorded for them (CLP). On `current` only. `null` = unknown (the credit is still warming); absent on `previous` and on the sample."
              },
              "first_payment_attributed_revenue": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "description": "CASH (CLP): the period’s FIRST payments of a plan (a clinic’s first pago of an accepted presupuesto) that the attribution engine credited to an ad, at their credited share — the numerator of «Retorno en primer pago». Part of `revenue`, never added to it. On `current` only; `null` = unknown (the credit is still warming)."
              },
              "patient_return": {
                "$ref": "#/components/schemas/AdsPatientReturnTotal"
              },
              "people": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "DISTINCT people behind the period’s recorded outcomes (every stage counted once per person) plus the people who FIRST WROTE in it, on the real message time (an imported history keeps its original timestamps) — the denominator of «Resultados desde anuncios». `outcomes` sums events, not people. On `current` only; `null` = unknown."
              },
              "attributed_people": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `people`, the people from ads (ADR «Ad return = ad-opened conversations»): their first message in the window came from an ad («llegaron») or a later conversation an ad opened in the window brought them back («volvieron»). On `current` only; `null` = unknown."
              },
              "attributed_people_arrived": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `attributed_people`, those whose FIRST message in the window came from an ad («llegaron por un anuncio»). On `current` only."
              },
              "attributed_people_returned": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `attributed_people`, those who came back: a later conversation an ad opened in the window («volvieron por un anuncio»). `attributed_people_arrived + attributed_people_returned = attributed_people`. On `current` only."
              },
              "people_window": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "description": "The days `people` / `attributed_people` count over: the period clipped to `coverage.people.measured_from` (the people family). Salud’s «Resultados por origen» counts the same window. On `current` only."
              },
              "people_spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Spend over exactly `people_window` — the only spend a cost per person divides. On `current` only; `null` = unknown."
              },
              "joins_state": {
                "type": "string",
                "enum": [
                  "ready",
                  "pending",
                  "unavailable"
                ],
                "description": "Why `attributed_pipeline`, `first_payment_attributed_revenue`, `people` and `attributed_people` are what they are: `ready` = computed; `pending` = the attribution credit is being computed right now — show «Calculando…» and read again in a few seconds (the read never waits for it); `unavailable` = unknown («—»). On `current` only."
              },
              "joins_complete": {
                "type": "boolean",
                "description": "`false` when the attribution credit behind the joins could not be read in full (a page of a campaign’s conversions could not be served): the people, accepted-plan and first-payment figures may undercount. Say so next to them. On `current` only."
              }
            },
            "required": [
              "spend",
              "spend_available",
              "clicks",
              "revenue",
              "organic_direct_revenue",
              "roas",
              "outcomes",
              "attributed_outcomes",
              "attributed_cash_outcomes",
              "coverage_percent",
              "cash_revenue",
              "cash_payments",
              "first_payment_revenue",
              "first_payment_customers",
              "recurring_revenue",
              "recurring_customers",
              "refunds",
              "refund_amount",
              "disputes",
              "dispute_amount",
              "dispute_reversed_amount",
              "outcome_counts"
            ]
          },
          "previous": {
            "type": "object",
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "spend_available": {
                "type": "boolean"
              },
              "clicks": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "revenue": {
                "type": "number"
              },
              "organic_direct_revenue": {
                "type": "number"
              },
              "roas": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "outcomes": {
                "type": "integer"
              },
              "attributed_outcomes": {
                "type": "integer"
              },
              "attributed_cash_outcomes": {
                "type": "integer"
              },
              "coverage_percent": {
                "type": "number"
              },
              "cash_revenue": {
                "type": "number",
                "description": "«Todo lo cobrado»: every payment received in the period (CLP), ad or not. See `cash_source`."
              },
              "cash_payments": {
                "type": "integer"
              },
              "cash_source": {
                "type": "string",
                "enum": [
                  "clinic_ledger",
                  "engine"
                ],
                "description": "Where `cash_revenue` / `cash_payments` come from: `clinic_ledger` = the clinic software (Dentalink / Medilink), the ONLY payment source while it is connected — every payment, linked to a contact or not; `engine` = the attribution total (no clinic software). Absent on the sample."
              },
              "first_payment_revenue": {
                "type": "number"
              },
              "first_payment_customers": {
                "type": "integer",
                "description": "The engine’s distinct NEW paying customers in the window, ad or not (the CAC denominator). It is NOT «patients who paid from ads»: that count is `patient_return.patients` (the Resumen hero, the campaigns totals and the Monday brief all read it)."
              },
              "recurring_revenue": {
                "type": "number"
              },
              "recurring_customers": {
                "type": "integer"
              },
              "refunds": {
                "type": "integer"
              },
              "refund_amount": {
                "type": "number"
              },
              "disputes": {
                "type": "integer"
              },
              "dispute_amount": {
                "type": "number"
              },
              "dispute_reversed_amount": {
                "type": "number"
              },
              "outcome_counts": {
                "type": "object",
                "additionalProperties": {
                  "type": "integer"
                }
              },
              "attributed_pipeline": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "closed_count": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "closed_value": {
                    "type": "number",
                    "minimum": 0
                  }
                },
                "required": [
                  "closed_count",
                  "closed_value"
                ],
                "description": "PIPELINE, never cash (not in `revenue` / `roas`): the period’s closes (`closed_won` — a clinic’s accepted plans, a dealer’s closed sales) that the attribution engine credited to an ad, with the value Vitrina recorded for them (CLP). On `current` only. `null` = unknown (the credit is still warming); absent on `previous` and on the sample."
              },
              "first_payment_attributed_revenue": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "description": "CASH (CLP): the period’s FIRST payments of a plan (a clinic’s first pago of an accepted presupuesto) that the attribution engine credited to an ad, at their credited share — the numerator of «Retorno en primer pago». Part of `revenue`, never added to it. On `current` only; `null` = unknown (the credit is still warming)."
              },
              "patient_return": {
                "$ref": "#/components/schemas/AdsPatientReturnTotal"
              },
              "people": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "DISTINCT people behind the period’s recorded outcomes (every stage counted once per person) plus the people who FIRST WROTE in it, on the real message time (an imported history keeps its original timestamps) — the denominator of «Resultados desde anuncios». `outcomes` sums events, not people. On `current` only; `null` = unknown."
              },
              "attributed_people": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `people`, the people from ads (ADR «Ad return = ad-opened conversations»): their first message in the window came from an ad («llegaron») or a later conversation an ad opened in the window brought them back («volvieron»). On `current` only; `null` = unknown."
              },
              "attributed_people_arrived": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `attributed_people`, those whose FIRST message in the window came from an ad («llegaron por un anuncio»). On `current` only."
              },
              "attributed_people_returned": {
                "type": [
                  "integer",
                  "null"
                ],
                "minimum": 0,
                "description": "Of `attributed_people`, those who came back: a later conversation an ad opened in the window («volvieron por un anuncio»). `attributed_people_arrived + attributed_people_returned = attributed_people`. On `current` only."
              },
              "people_window": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "from": {
                    "type": "string"
                  },
                  "to": {
                    "type": "string"
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "description": "The days `people` / `attributed_people` count over: the period clipped to `coverage.people.measured_from` (the people family). Salud’s «Resultados por origen» counts the same window. On `current` only."
              },
              "people_spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Spend over exactly `people_window` — the only spend a cost per person divides. On `current` only; `null` = unknown."
              },
              "joins_state": {
                "type": "string",
                "enum": [
                  "ready",
                  "pending",
                  "unavailable"
                ],
                "description": "Why `attributed_pipeline`, `first_payment_attributed_revenue`, `people` and `attributed_people` are what they are: `ready` = computed; `pending` = the attribution credit is being computed right now — show «Calculando…» and read again in a few seconds (the read never waits for it); `unavailable` = unknown («—»). On `current` only."
              },
              "joins_complete": {
                "type": "boolean",
                "description": "`false` when the attribution credit behind the joins could not be read in full (a page of a campaign’s conversions could not be served): the people, accepted-plan and first-payment figures may undercount. Say so next to them. On `current` only."
              }
            },
            "required": [
              "spend",
              "spend_available",
              "clicks",
              "revenue",
              "organic_direct_revenue",
              "roas",
              "outcomes",
              "attributed_outcomes",
              "attributed_cash_outcomes",
              "coverage_percent",
              "cash_revenue",
              "cash_payments",
              "first_payment_revenue",
              "first_payment_customers",
              "recurring_revenue",
              "recurring_customers",
              "refunds",
              "refund_amount",
              "disputes",
              "dispute_amount",
              "dispute_reversed_amount",
              "outcome_counts"
            ]
          },
          "days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsOverviewDay"
            },
            "description": "`grain=day` only: every day of the window, oldest first."
          },
          "previous_days": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsOverviewDay"
            },
            "description": "`grain=day` only: every day of the previous window of equal length."
          },
          "gaps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsGap"
            },
            "description": "Measurement gaps overlapping the window (W1.1). Days inside a gap whose `metrics` list a figure carry NO measured value for it — the engine may report 0 there; render «sin datos», never the 0."
          },
          "coverage": {
            "$ref": "#/components/schemas/AdsCoverage"
          },
          "meta_send": {
            "$ref": "#/components/schemas/AdsMetaSendMarker"
          },
          "conversion_source": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "crm": {
                "$ref": "#/components/schemas/AdsConversionSourceCrm"
              },
              "payments": {
                "$ref": "#/components/schemas/AdsConversionSourcePayments"
              }
            },
            "required": [
              "crm",
              "payments"
            ],
            "description": "The workspace’s Conversion source (see `GET /ads/conversion-source`); `null` when it could not be read this time — unknown, never «Vitrina»."
          }
        },
        "required": [
          "current",
          "previous",
          "gaps",
          "coverage",
          "meta_send",
          "conversion_source"
        ]
      },
      "AdsMeasurementIncident": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "cause": {
            "type": "string",
            "enum": [
              "meta_disconnected",
              "meta_stale",
              "tag_silent",
              "capi_failing",
              "whatsapp_disconnected",
              "engine_facts_missing"
            ]
          },
          "label": {
            "type": "string",
            "description": "«Meta Ads desconectado»."
          },
          "sentence": {
            "type": "string",
            "description": "«Tus anuncios de Meta dejaron de medirse el 12 sep»."
          },
          "action": {
            "type": "object",
            "properties": {
              "label": {
                "type": "string",
                "description": "«Reconectar Meta»."
              },
              "path": {
                "type": "string",
                "description": "Workspace-relative path of the ONE fix, e.g. `/anuncios/activar?paso=meta`."
              }
            },
            "required": [
              "label",
              "path"
            ],
            "description": "The one primary action."
          },
          "since": {
            "type": "string",
            "description": "When the data stopped (ISO instant)."
          },
          "opened_at": {
            "type": "string",
            "description": "When the watchdog opened it (ISO)."
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "resolution": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "reconnected",
              "recovered",
              "withdrawn"
            ],
            "description": "`reconnected` — a member fixed it; `recovered` — the leg came back on its own; `withdrawn` — it was switched off on purpose."
          },
          "resolved_by_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member who fixed it. `null` when it recovered on its own, or when the caller lacks `memberships:read`."
          },
          "metrics": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "spend",
                "conversations",
                "outcomes"
              ]
            }
          }
        },
        "required": [
          "id",
          "cause",
          "label",
          "sentence",
          "action",
          "since",
          "opened_at",
          "resolved_at",
          "resolution",
          "resolved_by_name",
          "metrics"
        ]
      },
      "AdsMeasurement": {
        "type": "object",
        "properties": {
          "open": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsMeasurementIncident"
            },
            "description": "Open incidents, oldest first — the banner."
          },
          "recent": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsMeasurementIncident"
            },
            "description": "Incidents resolved in the last 14 days, newest first."
          }
        },
        "required": [
          "open",
          "recent"
        ]
      },
      "AdsConversational": {
        "type": "object",
        "properties": {
          "data_complete": {
            "type": "boolean",
            "description": "`false` when a read behind the line failed or timed out: the `null` figures are unknown, not zero («sin datos de Meta por ahora»). `true` = every figure was read."
          },
          "objective": {
            "type": [
              "string",
              "null"
            ],
            "description": "The campaign objective as the ad platform names it (`OUTCOME_ENGAGEMENT`, `MESSAGES`, …); `null` when unknown."
          },
          "started": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "conversations": {
                "type": "integer",
                "description": "Conversations started, as the ad platform reports them."
              },
              "cost_per_conversation": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The row’s `spend` ÷ conversations started, whole CLP — the same spend basis as `vitrina.cost_per_conversation`; `null` when unknown."
              }
            },
            "required": [
              "conversations",
              "cost_per_conversation"
            ],
            "description": "The ad platform’s own count («conversaciones iniciadas», its 7-day window, every sender) — a DIFFERENT population from `vitrina`; never add or compare the two. `null` when it reports none."
          },
          "vitrina": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "conversations": {
                "type": "integer",
                "minimum": 1,
                "description": "Conversations whose first message came from this ad (or an ad of this campaign), opened in the period (America/Santiago days) — Vitrina’s own count."
              },
              "replied": {
                "type": "integer",
                "description": "«Respondidas»: of those, answered at least once by the team, the AI agent or an integration."
              },
              "appointments": {
                "type": "integer",
                "description": "Of those, with a booked appointment (cita / visita) recorded against them, whenever it was booked."
              },
              "closed": {
                "type": "integer",
                "description": "Of those, closed (a sale / an accepted plan) recorded against them, whenever it closed."
              },
              "cost_per_conversation": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The row’s `spend` ÷ `conversations`, whole CLP — the same spend basis as `started.cost_per_conversation`."
              }
            },
            "required": [
              "conversations",
              "replied",
              "appointments",
              "closed",
              "cost_per_conversation"
            ],
            "description": "Vitrina’s own conversations from the ad — deterministic counts, never a credited share. `null` when Vitrina counts none in the period (or the ad → campaign map could not be read now)."
          }
        },
        "required": [
          "data_complete",
          "objective",
          "started",
          "vitrina"
        ],
        "description": "Present on a conversational row: a message-only objective (`MESSAGES`), or conversations the platform reports, or conversations Vitrina counts. Also present with `data_complete: false` (figures `null`) when a read failed on a row that may be conversational (its objective unknown or in the messaging family) in a workspace with ad-opened conversations. ABSENT otherwise — never a row of zeros, never partial counts."
      },
      "AdsCampaignWebsite": {
        "type": "object",
        "properties": {
          "clicks": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "description": "Meta’s clicks on the campaign over the row’s days. `null` = the delivery read failed (unknown, never 0)."
          },
          "forms": {
            "type": "integer",
            "minimum": 0,
            "description": "Landing-page forms («Formulario web», `web_form`)."
          },
          "bookings": {
            "type": "integer",
            "minimum": 0,
            "description": "Bookings (`appointment_booked`) — «citas» for a clinic."
          },
          "checkouts_started": {
            "type": "integer",
            "minimum": 0,
            "description": "Payment links opened («Abrió el link de pago», `checkout_started`): the person opened a Vitrina payment link."
          },
          "payments": {
            "type": "integer",
            "minimum": 0,
            "description": "Payments (`payment_received`) — every one: a trial, an evaluation and a later treatment payment each count."
          },
          "revenue": {
            "type": "number",
            "minimum": 0,
            "description": "CLP of those payments, whole pesos."
          },
          "roas": {
            "type": [
              "number",
              "null"
            ],
            "description": "`revenue ÷ spend`; `null` without spend."
          },
          "by_source": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "source": {
                  "type": "string",
                  "enum": [
                    "vitrina",
                    "gohighlevel",
                    "mercadopago"
                  ],
                  "description": "Who recorded the results: `vitrina` (Vitrina’s own booking page, widget, agenda or caja), `gohighlevel`, `mercadopago` (the connected provider, through the attribution engine)."
                },
                "forms": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Landing-page forms («Formulario web», `web_form`)."
                },
                "bookings": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Bookings (`appointment_booked`) — «citas» for a clinic."
                },
                "checkouts_started": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Payment links opened («Abrió el link de pago», `checkout_started`): the person opened a Vitrina payment link."
                },
                "payments": {
                  "type": "integer",
                  "minimum": 0,
                  "description": "Payments (`payment_received`) — every one: a trial, an evaluation and a later treatment payment each count."
                },
                "revenue": {
                  "type": "number",
                  "minimum": 0,
                  "description": "CLP of those payments, whole pesos."
                }
              },
              "required": [
                "source",
                "forms",
                "bookings",
                "checkouts_started",
                "payments",
                "revenue"
              ]
            },
            "description": "The same figures split by `source`, only the sources with a result: each count and `revenue` add up to the totals above exactly."
          }
        },
        "required": [
          "clicks",
          "forms",
          "bookings",
          "checkouts_started",
          "payments",
          "revenue",
          "roas",
          "by_source"
        ],
        "description": "A WEBSITE AD campaign’s landing-page funnel (#3708): clicks → forms → bookings → checkouts started → payments, their revenue and return. Every count is an outcome `/ads/feed` lists that the attribution engine credited to THIS campaign in the window (the same rows Atribuidos shows with this `ad.campaign`); `revenue` is every credited payment of the window, not only the first (later payments count inside the engine’s attribution window). Present only when the campaign has website evidence in the window — a credited form or booking a connected CRM recorded, or a credited booking / checkout on Vitrina’s booking page or widget. ABSENT on a click-to-WhatsApp campaign and while the credit is unknown (still warming, or a campaign outside the credited set read: the top 25, the last 92 days)."
      },
      "AdsBookingDrafts": {
        "type": "object",
        "properties": {
          "abandoned": {
            "type": "integer",
            "minimum": 0,
            "description": "Bookings started on Vitrina’s booking page or widget (the patient reached the details step and typed a phone) and left unfinished: the draft expired, was recovered, or is still open and was claimed by the recovery sweep or untouched for the workspace’s abandonment delay (default 60 min). A booking finished without a message is never abandoned. Counted on the draft’s creation day (Santiago)."
          },
          "recovered": {
            "type": "integer",
            "minimum": 0,
            "description": "Of `abandoned`, the bookings won back by the recovery message (the patient booked through its link). Always ≤ `abandoned`."
          }
        },
        "required": [
          "abandoned",
          "recovered"
        ],
        "description": "Abandoned and recovered bookings (#3709) filed on this row by the ad click the draft carried, the way the attribution engine resolves a website click: the first of `utm_content`, `utm_term`, `utm_id` that names a known ad (→ that ad and its campaign) or campaign. Ids, never names. Present on EVERY row (zeros real) when the workspace has at least one abandoned draft in the window; ABSENT on every row otherwise (no booking surface, nothing abandoned, or the read failed)."
      },
      "AdsCampaigns": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "campaign_id": {
                  "type": "string"
                },
                "campaign_name": {
                  "type": "string"
                },
                "campaign_external_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "spend": {
                  "type": "number"
                },
                "outcome_count": {
                  "type": "integer",
                  "description": "Conversions the attribution engine credits to this campaign over the period, under `model`. Today the engine counts CASH conversions only (payments; `revenue_type = cash`) — a lead, a cita or an accepted plan without a payment is not in it, so a workspace with no payments recorded reads 0 on every row. Not the population of `/ads/overview` `attributed_outcomes` (every kind) nor of `closed_outcome_count`."
                },
                "outcome_value": {
                  "type": "number"
                },
                "roas": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Attributed value ÷ spend. `null` when the measurement start could not be read (`coverage.start_unknown`): unknown, never 0."
                },
                "direct_cash_value": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "inherited_cash_value": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "people": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 0,
                  "description": "PEOPLE the campaign brought, in the headline’s own definition and window (`/ads/overview` `current.attributed_people`, over `current.people_window`): the people whose FIRST message in the window came from one of its ads — each person on exactly one campaign, so the rows plus `people.unplaced` add up to the headline. `null` = unknown."
                },
                "people_spend": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "minimum": 0,
                  "description": "The campaign’s spend over the SAME days `people` counts (the people window) — the only spend a cost per person may divide. Equals `spend` when the people window is the row’s window. `null` = unknown."
                },
                "closed_outcome_count": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 0,
                  "description": "Closes over the WHOLE period credited to this campaign: the outcomes Vitrina recorded at the closed stage (`closed_won`) with `occurred_at` in the period (America/Santiago days) that the attribution engine matched to an ad, each counted once on its largest-credit campaign — the same rows the feed shows as `stage: closed_won, matched: true` with this `ad.campaign`, so it does not move while paging the feed. `null` = unknown (never 0): the campaign credit is not available yet (it warms in the background; retry shortly), the campaign is outside the credited set read (the top 25 by outcomes), or the period starts more than 92 days ago."
                },
                "top_ad": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "external_id": {
                      "type": "string",
                      "description": "The ad’s platform (Meta) id."
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "creative_thumbnail_url": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "video_external_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Meta’s id of the ad’s video; `null` for an image ad. Play it with `GET /ads/ads/{id}/video` (the source is short-lived: ask on play)."
                    },
                    "ads_manager_url": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The ad in Meta Ads Manager (opens in a new tab); `null` when the ad account is unknown."
                    }
                  },
                  "required": [
                    "external_id",
                    "name",
                    "creative_thumbnail_url"
                  ],
                  "description": "The campaign’s highest-spend ad among the top-50 scored ads of the creatives read (the rolling window nearest this range: 7d / 14d / 28d); `null` when unknown."
                },
                "series": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "spend": {
                        "type": "number"
                      },
                      "outcome_count": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "description": "Conversions credited primarily to the campaign that day (each conversion counted once, on its largest-credit campaign). `null` when unknown: credit not available yet, the campaign outside the credited set read (top 25 by outcomes), or a day before the credit window (the last 92 days). Under a multi-touch model the days can sum to less than the row’s `outcome_count`."
                      }
                    },
                    "required": [
                      "date",
                      "spend",
                      "outcome_count"
                    ]
                  },
                  "description": "`series=1` only. `null` when the daily delivery read failed. A day without delivery is `spend: 0`."
                },
                "conversational": {
                  "$ref": "#/components/schemas/AdsConversational"
                },
                "patient_return": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdsPatientReturn"
                    },
                    {
                      "description": "The payments of the period counted toward ad return whose counting ad (the ad-opened conversation) belongs to this campaign; `null` = unknown."
                    }
                  ]
                },
                "appointments_booked": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 0,
                  "description": "Citas: the campaign’s conversations (opened in `metric_windows.appointments`) with a booked or attended appointment — `conversational.vitrina.appointments`. `null` = Vitrina counts no conversations for the row (unknown, not 0)."
                },
                "cost_per_appointment": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Cost per cita, whole CLP: the spend of `metric_windows.appointments` ÷ `appointments_booked`. `null` (never 0) with no spend or no cita."
                },
                "paying_patients": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "minimum": 0,
                  "description": "Distinct ad patients with a counted payment in `metric_windows.paying_patients` (= `patient_return.patients`). `null` = unknown."
                },
                "cost_per_paying_patient": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Cost per paying patient, whole CLP: the row’s `spend` ÷ `paying_patients`. `null` (never 0) with no spend or nobody paid."
                },
                "website": {
                  "$ref": "#/components/schemas/AdsCampaignWebsite"
                },
                "booking_drafts": {
                  "$ref": "#/components/schemas/AdsBookingDrafts"
                }
              },
              "required": [
                "campaign_id",
                "campaign_name",
                "campaign_external_id",
                "spend",
                "outcome_count",
                "outcome_value",
                "roas",
                "direct_cash_value",
                "inherited_cash_value",
                "closed_outcome_count",
                "top_ad"
              ]
            }
          },
          "coverage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsCoverage"
              },
              {
                "description": "The coverage window the rows were read over (see `AdsCoverage`): spend, results and return all count from `coverage.from`."
              }
            ]
          },
          "people": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "attributed": {
                "type": "integer",
                "minimum": 0,
                "description": "People from ads: exactly `/ads/overview` `current.attributed_people` for the same window."
              },
              "placed": {
                "type": "integer",
                "minimum": 0,
                "description": "Of `attributed`, the people a row of `data` counts: exactly the sum of the rows’ `people`."
              },
              "unplaced": {
                "type": "integer",
                "minimum": 0,
                "description": "`attributed − placed`: people from ads no row places yet. The only remainder Campañas prints."
              }
            },
            "required": [
              "attributed",
              "placed",
              "unplaced"
            ],
            "description": "The rows reconciled with the headline, in PEOPLE. `null`/absent = not known yet (the credit is warming): print no remainder."
          },
          "totals": {
            "type": "object",
            "properties": {
              "spend": {
                "type": "number",
                "description": "Sum of the rows’ spend."
              },
              "appointments_booked": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "cost_per_appointment": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "paying_patients": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "ACCOUNT-level distinct paying ad patients — `/ads/overview` `current.patient_return.patients` for the same window — so it can exceed the rows’ sum (people no campaign row places)."
              },
              "cost_per_paying_patient": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "spend",
              "appointments_booked",
              "cost_per_appointment",
              "paying_patients",
              "cost_per_paying_patient"
            ],
            "description": "The totals row of the cost columns. Same rules as the rows: `null`, never 0."
          },
          "patient_return_unplaced": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsPatientReturn"
              },
              {
                "description": "«Anuncios aún sin campaña conocida»: the patient return of the payments no row of `data` holds (their ad has no known campaign, or its campaign has no row in the window) — rows + this = `/ads/overview` `current.patient_return` in CLP and payments. `GET /ads/people?metric=paid_patients&unplaced=1` lists exactly these `patients`. Print it as is; never subtract the rows from the total. `null`/absent = unknown."
              }
            ]
          },
          "best_campaign": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "named",
                  "none",
                  "unknown"
                ],
                "description": "`named` = `campaign` is the best campaign; `none` = no campaign qualifies (see `reason`); `unknown` = the measurement start is unknown."
              },
              "campaign": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "campaign_id": {
                    "type": "string"
                  },
                  "campaign_external_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "campaign_name": {
                    "type": "string"
                  },
                  "spend": {
                    "type": "number"
                  },
                  "payments": {
                    "type": "integer",
                    "description": "The payments its return counts."
                  },
                  "patients": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "value": {
                    "type": "number",
                    "description": "CLP paid (the numerator of `return`)."
                  },
                  "return": {
                    "type": "number",
                    "description": "`value ÷ spend`, the metric of `rule`."
                  }
                },
                "required": [
                  "campaign_id",
                  "campaign_external_id",
                  "campaign_name",
                  "spend",
                  "payments",
                  "patients",
                  "value",
                  "return"
                ]
              },
              "rule": {
                "type": "object",
                "properties": {
                  "metric": {
                    "type": "string",
                    "enum": [
                      "patient_return",
                      "first_payment_return"
                    ]
                  },
                  "min_payments": {
                    "type": "integer"
                  },
                  "window": {
                    "type": "object",
                    "properties": {
                      "from": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "to": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      }
                    },
                    "required": [
                      "from",
                      "to"
                    ]
                  },
                  "description": {
                    "type": "string",
                    "description": "The rule in plain Spanish, for display."
                  }
                },
                "required": [
                  "metric",
                  "min_payments",
                  "window",
                  "description"
                ]
              },
              "reason": {
                "type": "string",
                "enum": [
                  "highest_return",
                  "no_campaign_has_min_payments",
                  "no_paid_campaign",
                  "start_unknown"
                ]
              }
            },
            "required": [
              "state",
              "campaign",
              "rule",
              "reason"
            ],
            "description": "THE «best campaign», one definition for every screen (Resumen, Campañas, the briefing facts): the highest `rule.metric` among campaigns with spend and at least `rule.min_payments` payments in `rule.window` (the covered window of this read); ties go to the campaign with more paying patients. `patient_return` = total return of its ad patients (first payment + cuotas) ÷ spend. Display `campaign.return` as is — never recompute it or rank the rows again. The per-AD winner (`/ads/scorecard` `best_ad_external_id`) is a different, per-ad ranking."
          },
          "metric_windows": {
            "type": "object",
            "properties": {
              "appointments": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              },
              "paying_patients": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              },
              "best_campaign": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              }
            },
            "required": [
              "appointments",
              "paying_patients",
              "best_campaign"
            ],
            "description": "The window each new figure is counted over: `appointments` = the conversations’ own window (spend divided over the same days), `paying_patients` and `best_campaign` = the covered window."
          },
          "gaps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsGap"
            },
            "description": "Measurement gaps overlapping the window (W1.1). Days inside a gap whose `metrics` list a figure carry NO measured value for it — the engine may report 0 there; render «sin datos», never the 0."
          },
          "joins_state": {
            "type": "string",
            "enum": [
              "ready",
              "pending",
              "unavailable"
            ],
            "description": "Why every row's `patient_return` is what it is (the same vocabulary as `/ads/overview` `current.joins_state`): `ready` = computed (a row's `null` = that campaign's credit was not read — unknown); `pending` = the attribution credit is being read right now — show «Calculando…» and read again in a few seconds; `unavailable` = the read failed or the measurement start is unknown — show «—», never a spinner."
          }
        },
        "required": [
          "data",
          "gaps"
        ]
      },
      "AdsLinkedPatient": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The ROSTER patient id (`clinic_patient.id`) — the id `GET /clinic/patients/{id}` loads. Never the contact id: link the ficha with this, the contact with `contact.id`."
          },
          "name": {
            "type": "string",
            "description": "The roster’s «nombre apellidos». Identity-gated (below)."
          },
          "rut": {
            "type": "string",
            "description": "The patient’s RUT as the roster stores it. Identity-gated (below). Read from the clinic’s own roster at response time; never sent to the attribution engine or to Meta."
          },
          "phone": {
            "type": "string",
            "description": "The roster’s phone. Identity-gated (below)."
          }
        },
        "required": [
          "id"
        ],
        "description": "The linked roster patient; `null` unless `patient_link` is `linked`. For a payment it is the ficha the payment was FOR (a parent paying for a child is the child), else the contact’s own patient. **Identity gate:** `name`, `rut` and `phone` are present only when the roster has the value AND the caller may read the clinic register — `contacts:read` + `clinic:read` (a signed-in member), plus `clinic_patients:read` for an API key / token. Absent (never blank) otherwise; `id` is always present."
      },
      "AdsAttributedPerson": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Stable within one response: the roster patient id, else the contact id, else the engine’s customer / conversion id."
          },
          "patient_link": {
            "type": "string",
            "enum": [
              "linked",
              "none",
              "unknown"
            ],
            "description": "Whether this is a clinic ROSTER patient: `linked` — `patient` is set; `none` — it resolves to no roster patient (not a patient, or a non-clinic workspace): link to the contact instead; `unknown` — the roster could not be read right now (a lookup failure, NOT «no patient»): retry, never say «no vinculado»."
          },
          "patient": {
            "$ref": "#/components/schemas/AdsLinkedPatient"
          },
          "contact": {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The CONTACT id, never a roster id; `null` when unknown."
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The contact’s name (`contacts:read`), else the roster patient’s (identity gate); `null` otherwise."
              },
              "key": {
                "type": "string",
                "description": "Last 4 hex of the contact id."
              }
            },
            "required": [
              "id",
              "display_name",
              "key"
            ]
          },
          "total_paid": {
            "type": "number",
            "description": "Sum of `payments[].amount`, CLP; 0 when nobody paid yet."
          },
          "credited_paid": {
            "type": "number",
            "description": "Sum of `payments[].credited_value`, CLP."
          },
          "payment_count": {
            "type": "integer"
          },
          "first_payment_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_payment_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "stages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Every stage credited to the campaign for this person, funnel order (`lead_created` … `payment_received`)."
          },
          "origin_ad_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The ads (platform ids) that opened a conversation with this person — what `ad_id` filters on."
          },
          "payments": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "conversion_id": {
                  "type": "string"
                },
                "outcome_event_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "paid_at": {
                  "type": "string",
                  "description": "ISO instant of the payment."
                },
                "amount": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The payment’s full amount, CLP; `null` when unknown."
                },
                "credited_value": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The share of it credited to the campaign under `model`."
                },
                "is_first_payment": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                },
                "is_inherited": {
                  "type": "boolean"
                }
              },
              "required": [
                "conversion_id",
                "outcome_event_id",
                "paid_at",
                "amount",
                "credited_value",
                "is_first_payment",
                "is_inherited"
              ]
            },
            "description": "The `payment_received` conversions, oldest first."
          }
        },
        "required": [
          "key",
          "patient_link",
          "patient",
          "contact",
          "total_paid",
          "credited_paid",
          "payment_count",
          "first_payment_at",
          "last_payment_at",
          "stages",
          "origin_ad_ids",
          "payments"
        ]
      },
      "AdsAttributedSales": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "conversion_id": {
                  "type": "string"
                },
                "event_type": {
                  "type": "string"
                },
                "event_time": {
                  "type": "string"
                },
                "customer_profile_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "customer_key": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "customer_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "customer_email": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "value_amount": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "revenue_type": {
                  "type": "string"
                },
                "credited_value": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "credited_weight": {
                  "type": "number"
                },
                "touch_count": {
                  "type": "integer"
                },
                "path_length": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "first_touch_time": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "last_touch_time": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "is_inherited": {
                  "type": "boolean"
                }
              },
              "required": [
                "conversion_id",
                "event_type",
                "event_time",
                "customer_profile_id",
                "customer_key",
                "customer_name",
                "customer_email",
                "value_amount",
                "revenue_type",
                "credited_value",
                "credited_weight",
                "touch_count",
                "path_length",
                "first_touch_time",
                "last_touch_time",
                "is_inherited"
              ]
            }
          },
          "people": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsAttributedPerson"
            },
            "description": "The same conversions, ONE entry per person (the linked roster patient when there is one, else the contact): payments nested and summed, whoever paid most first. Filtered by `ad_id` when given."
          },
          "people_state": {
            "type": "string",
            "enum": [
              "ready",
              "unavailable"
            ],
            "description": "`unavailable`: the roster or the outcome refs could not be read — every entry then says `patient_link: unknown`. Retry."
          }
        },
        "required": [
          "data",
          "people",
          "people_state"
        ]
      },
      "AdsPersonRow": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Stable within one response: the roster patient id, else the contact id, else `conversation:<id>`."
          },
          "patient_link": {
            "type": "string",
            "enum": [
              "linked",
              "none",
              "unknown"
            ],
            "description": "Whether this is a clinic ROSTER patient: `linked` — `patient` is set; `none` — it resolves to no roster patient (not a patient, or a non-clinic workspace): link to the contact instead; `unknown` — the roster could not be read right now (a lookup failure, NOT «no patient»): retry, never say «no vinculado»."
          },
          "patient": {
            "$ref": "#/components/schemas/AdsLinkedPatient"
          },
          "contact": {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The CONTACT id, never a roster id; `null` when unknown."
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The contact’s name (`contacts:read`), else the roster patient’s (identity gate); `null` otherwise."
              },
              "key": {
                "type": "string",
                "description": "Last 4 hex of the contact id."
              }
            },
            "required": [
              "id",
              "display_name",
              "key"
            ]
          },
          "origin": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "ad_external_id": {
                "type": "string",
                "description": "Platform (Meta) ad id."
              },
              "campaign_external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Platform campaign id; `null` = no known campaign."
              }
            },
            "required": [
              "ad_external_id",
              "campaign_external_id"
            ],
            "description": "Patient return: the ad (and its campaign) that counts the person’s first payment of the scope. Conversations: the ad that opened it. `null` on the funnel (it has no ad grain)."
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "contacted",
              "booked",
              "attended",
              "quote_presented",
              "accepted",
              "paid"
            ],
            "description": "Funnel metrics: the furthest step reached in the window."
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "arrived",
              "returned"
            ],
            "description": "«llegaron» (first message from an ad) / «volvieron por un anuncio». Patient return: by the first counted payment of the scope. `null` on conversations."
          },
          "first_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Funnel: first contact (ISO). Conversations: when it started (ISO). Patient return: the first counted payment’s day."
          },
          "paid": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "total": {
                "type": "number",
                "description": "CLP, the scope’s counted payments."
              },
              "count": {
                "type": "integer"
              },
              "first_value": {
                "type": "number",
                "description": "Of `total`, the person’s first payment ever."
              },
              "payments": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "day": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                      "description": "Clinic (Santiago) day the payment was received."
                    },
                    "amount": {
                      "type": "number",
                      "description": "CLP."
                    },
                    "is_first_payment": {
                      "type": "boolean",
                      "description": "The person’s first payment ever (counted or not)."
                    },
                    "kind": {
                      "type": "string",
                      "enum": [
                        "arrived",
                        "returned"
                      ],
                      "description": "«llegaron» / «volvieron por un anuncio» for this payment."
                    },
                    "ad_external_id": {
                      "type": "string",
                      "description": "The ad that counts it (platform id)."
                    },
                    "campaign_external_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "That ad’s campaign (platform id); `null` = no known campaign."
                    }
                  },
                  "required": [
                    "day",
                    "amount",
                    "is_first_payment",
                    "kind",
                    "ad_external_id",
                    "campaign_external_id"
                  ]
                },
                "description": "Oldest first."
              }
            },
            "required": [
              "total",
              "count",
              "first_value",
              "payments"
            ],
            "description": "Patient-return metrics only."
          },
          "conversation": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "replied": {
                "type": "boolean"
              },
              "appointment": {
                "type": "boolean",
                "description": "A booked or attended cita."
              },
              "attended": {
                "type": "boolean"
              },
              "closed": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "replied",
              "appointment",
              "attended",
              "closed"
            ],
            "description": "Conversation metrics only."
          }
        },
        "required": [
          "key",
          "patient_link",
          "patient",
          "contact",
          "origin",
          "stage",
          "kind",
          "first_at",
          "paid",
          "conversation"
        ]
      },
      "AdsPeople": {
        "type": "object",
        "properties": {
          "metric": {
            "type": "string",
            "enum": [
              "paid_patients",
              "arrived_patients",
              "returned_patients",
              "funnel_contacted",
              "funnel_booked",
              "funnel_attended",
              "funnel_quote_presented",
              "funnel_accepted",
              "funnel_paid",
              "new_people",
              "new_people_booked",
              "new_people_paid",
              "returned_people",
              "returned_people_booked",
              "conversations",
              "conversations_replied",
              "conversations_booked",
              "conversations_attended",
              "conversations_closed"
            ]
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ],
            "description": "The days the population is counted over — the count’s own window (patient return: clipped to the first measured day, as `/ads/overview`; conversations: the `/ads/campaigns` conversational window)."
          },
          "scope": {
            "type": "object",
            "properties": {
              "campaign_external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ad_external_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "unplaced": {
                "type": "boolean"
              }
            },
            "required": [
              "campaign_external_id",
              "ad_external_id",
              "unplaced"
            ],
            "description": "The query’s `campaign_id` / `ad_id` / `unplaced`, echoed."
          },
          "count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The population’s size = the number on the screen, by construction (both come from one read). `null` = unknown (`state`)."
          },
          "people": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsPersonRow"
            },
            "description": "Patient return: who paid most first. Funnel: by first contact. Conversations: by start. Exactly `count` rows unless `truncated`."
          },
          "truncated": {
            "type": "boolean",
            "description": "`people` stops at `limit`; `count` is still the whole."
          },
          "state": {
            "type": "string",
            "enum": [
              "ready",
              "pending",
              "unavailable"
            ],
            "description": "`pending`: the patient read is warming — read again in a few seconds; `unavailable`: the read (or the campaign map behind the scope) failed — unknown, never «nadie»."
          },
          "people_state": {
            "type": "string",
            "enum": [
              "ready",
              "unavailable"
            ],
            "description": "`unavailable`: the roster could not be read — every row says `patient_link: unknown`. Retry."
          }
        },
        "required": [
          "metric",
          "window",
          "scope",
          "count",
          "people",
          "truncated",
          "state",
          "people_state"
        ]
      },
      "AdsCreatives": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ad_external_id": {
                  "type": "string"
                },
                "ad_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "campaign_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "campaign": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "external_id": {
                      "type": "string",
                      "description": "The campaign’s platform id = an `/ads/campaigns` row’s `campaign_external_id`. Join by this, never by name."
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "external_id",
                    "name"
                  ],
                  "description": "The ad’s campaign; `null` when the engine does not say."
                },
                "ad_set_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "creative_thumbnail_url": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "video_external_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Meta’s id of the ad’s video; `null` for an image ad. Play it with `GET /ads/ads/{id}/video` (the source is short-lived: ask on play)."
                },
                "ads_manager_url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The ad in Meta Ads Manager (opens in a new tab); `null` when the ad account is unknown."
                },
                "score_window": {
                  "type": "string"
                },
                "composite_score": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "top_performer_likelihood": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Passed through as the engine sends it: an UNVALIDATED ranker output. Not a probability to show — Vitrina derives nothing from it."
                },
                "maturity_stage": {
                  "type": "string"
                },
                "truth_grade": {
                  "type": "string"
                },
                "primary_outcome_kind": {
                  "type": "string"
                },
                "spend": {
                  "type": "number"
                },
                "impressions": {
                  "type": "integer"
                },
                "ctr": {
                  "type": "number"
                },
                "attributed_revenue": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "roas": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "fatigue_state": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "active",
                    "paused",
                    "degraded"
                  ],
                  "description": "The OBSERVED delivery state. `degraded` = the ad «se desgasta»: on 3 consecutive days its trailing 7-day CTR was at or under 70 % of its own previous 28 days, or its trailing 7-day cost per result at or over 1/0.7× (+42.86 %) of it (≥ 1,000 impressions, ≥ 5 baseline results). The only input to every wear verdict, card and fact."
                },
                "fatigue_risk_tier": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "low",
                    "medium",
                    "high",
                    "critical"
                  ],
                  "description": "Passed through as the engine sends it: a learned risk tier that has not cleared an out-of-time evaluation. Vitrina derives nothing from it — key on `fatigue_state`."
                },
                "series": {
                  "type": [
                    "array",
                    "null"
                  ],
                  "items": {
                    "type": "object",
                    "properties": {
                      "date": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "spend": {
                        "type": "number"
                      },
                      "ctr": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "description": "Percent (clicks ÷ impressions × 100), like the row’s `ctr`; `null` on a day without impressions."
                      }
                    },
                    "required": [
                      "date",
                      "spend",
                      "ctr"
                    ]
                  },
                  "description": "`series=1` only: the last 14 days (America/Santiago). `null` past the first 50 rows or when this ad’s daily read failed (never a zero-filled line for an unknown); `spend: 0` days are measured no-delivery days."
                },
                "conversational": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdsConversational"
                    },
                    {
                      "description": "See `AdsConversational`. Vitrina’s counts cover the period the row’s `spend` covers: the rolling `window`’s days, the whole history for `lifetime`. For `lifetime` the objective is read over the latest year and `started` is `null` (the platform reports conversations over at most a year, not the lifetime spend’s period)."
                    }
                  ]
                },
                "booking_drafts": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdsBookingDrafts"
                    },
                    {
                      "description": "See `AdsBookingDrafts`: the drafts whose click names THIS ad, over the period the row’s `spend` covers."
                    }
                  ]
                }
              },
              "required": [
                "ad_external_id",
                "ad_name",
                "campaign_name",
                "campaign",
                "ad_set_name",
                "creative_thumbnail_url",
                "video_external_id",
                "ads_manager_url",
                "score_window",
                "composite_score",
                "top_performer_likelihood",
                "maturity_stage",
                "truth_grade",
                "primary_outcome_kind",
                "spend",
                "impressions",
                "ctr",
                "attributed_revenue",
                "roas",
                "fatigue_state",
                "fatigue_risk_tier"
              ]
            }
          },
          "coverage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsCoverage"
              },
              {
                "description": "The coverage of the rolling window’s dates (see `AdsCoverage`). The engine’s window is NOT clipped: when `clipped`, its spend and return include days before measurement began — say «Medimos desde {measured_from}» next to them."
              }
            ]
          }
        },
        "required": [
          "data"
        ]
      },
      "AdsScorecard": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "days": {
                "type": "integer"
              }
            },
            "required": [
              "from",
              "to",
              "days"
            ]
          },
          "coverage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsCoverage"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "basis": {
            "type": "string",
            "enum": [
              "accepted",
              "cash",
              "expected"
            ],
            "description": "What the ranking sorts by first: `expected` for a clinic with its economics known (each ad on its expected return, or its cash once it has 5 payments of its own — D63); otherwise the accepted-quote return, or cash once the window holds `cash_evidence.min_payments` payments."
          },
          "cash_evidence": {
            "type": "object",
            "properties": {
              "payments": {
                "type": "integer"
              },
              "min_payments": {
                "type": "integer"
              },
              "used_for_ranking": {
                "type": "boolean"
              }
            },
            "required": [
              "payments",
              "min_payments",
              "used_for_ranking"
            ]
          },
          "evidence": {
            "type": "object",
            "properties": {
              "thin": {
                "type": "boolean"
              },
              "days": {
                "type": "integer"
              },
              "min_days": {
                "type": "integer"
              }
            },
            "required": [
              "thin",
              "days",
              "min_days"
            ]
          },
          "funnel": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "people": {
                "type": "integer"
              },
              "booked": {
                "type": "integer"
              },
              "rate": {
                "type": "number"
              },
              "interval": {
                "type": "object",
                "properties": {
                  "low": {
                    "type": "number"
                  },
                  "high": {
                    "type": "number"
                  }
                },
                "required": [
                  "low",
                  "high"
                ]
              },
              "leaking": {
                "type": "boolean",
                "description": "Surely under the 10 % clinic floor (D66)."
              }
            },
            "required": [
              "people",
              "booked",
              "rate",
              "interval",
              "leaking"
            ],
            "description": "The account’s booking rate from ads."
          },
          "economics": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "show_rate": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "account_value": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "by_linea": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "linea_id": {
                      "type": "string"
                    },
                    "value": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "linea_id",
                    "value"
                  ]
                }
              }
            },
            "required": [
              "show_rate",
              "account_value",
              "by_linea"
            ],
            "description": "The clinic economics behind the expected values: cash per evaluated new patient (per Línea and overall) and the show rate."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "ad_external_id": {
                  "type": "string"
                },
                "ad_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "display_name": {
                  "type": "string",
                  "description": "The name to print: `ad_name`; an ad sharing its name with another is printed as «Campaña › Conjunto › Anuncio» (the id’s last digits follow only when even that path repeats)."
                },
                "ad_set_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "campaign_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "campaign_external_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "thumbnail_url": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "video_external_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Meta’s id of the ad’s video; `null` for an image ad. Play it with `GET /ads/ads/{id}/video` (the source is short-lived: ask on play)."
                },
                "ads_manager_url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The ad in Meta Ads Manager (opens in a new tab); `null` when the ad account is unknown."
                },
                "spend": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Spend over the covered window."
                },
                "people": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "People from ads whose first message in the people window came from this ad — the per-ad share of `/ads/campaigns` people (the same count, the same window): `ratios.cost_per_person` = the spend of those days ÷ this."
                },
                "booked_people": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "Distinct people whose conversation the ad opened, from the conversations’ own start (`coverage.conversations`): the population `booked`, `attended` and `accepted` are counted on («agendaron {booked} de {booked_people}»)."
                },
                "booked": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "attended": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "accepted": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "value": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "count",
                    "value"
                  ],
                  "description": "Accepted presupuestos / closed sales — pipeline, never cash."
                },
                "cash": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "first": {
                      "type": "number"
                    },
                    "recurring": {
                      "type": "number"
                    },
                    "total": {
                      "type": "number"
                    },
                    "payments": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "first",
                    "recurring",
                    "total",
                    "payments"
                  ]
                },
                "fatigue": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "active",
                    "paused",
                    "degraded"
                  ],
                  "description": "The OBSERVED delivery state (`degraded` = se desgasta)."
                },
                "ratios": {
                  "type": "object",
                  "properties": {
                    "accepted_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Accepted presupuestos / closed sales ÷ spend (×)."
                    },
                    "cash_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Cash (first + recurring payments) ÷ spend (×)."
                    },
                    "first_payment_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "First-payment cash ÷ spend (×)."
                    },
                    "cost_per_person": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The spend of the people window ÷ `people` (CLP) — the same division as a campaign’s «por persona»."
                    },
                    "expected_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The expected value of the ad’s bookings ÷ spend (×, D63): bookings × its Línea’s cash per evaluated new patient × the clinic’s show rate."
                    },
                    "cost_per_booking": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ bookings (CLP, «costo por cita agendada»)."
                    }
                  },
                  "required": [
                    "accepted_return",
                    "cash_return",
                    "first_payment_return",
                    "cost_per_person"
                  ]
                },
                "linea_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The Línea the clinic’s rules place the ad in (D53)."
                },
                "judgement": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "booking_rate": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "booked ÷ people (0–1)."
                    },
                    "booking_interval": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "low": {
                          "type": "number"
                        },
                        "high": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "low",
                        "high"
                      ],
                      "description": "95 % Wilson interval of the booking rate."
                    },
                    "comparator": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "rate": {
                          "type": "number"
                        },
                        "people": {
                          "type": "integer"
                        },
                        "booked": {
                          "type": "integer"
                        },
                        "median": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "source": {
                          "type": "string",
                          "enum": [
                            "linea",
                            "account"
                          ]
                        }
                      },
                      "required": [
                        "rate",
                        "people",
                        "booked",
                        "median",
                        "source"
                      ],
                      "description": "The other ads of its Línea (or of the account when the Línea has too few people) the ad is judged against."
                    },
                    "expected_bookings": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "people × the comparator’s booking rate."
                    },
                    "pause": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "confidence": {
                          "type": "string",
                          "enum": [
                            "seguro",
                            "probable",
                            "falta_data"
                          ],
                          "description": "`seguro` · `probable` · `falta_data` (with ≈ `people_needed` more people) — D64, never a fixed floor."
                        },
                        "people_needed": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "confidence",
                        "people_needed"
                      ],
                      "description": "Zero bookings: how sure a pause is."
                    },
                    "scale": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "confidence": {
                          "type": "string",
                          "enum": [
                            "seguro",
                            "probable",
                            "falta_data"
                          ],
                          "description": "`seguro` · `probable` · `falta_data` (with ≈ `people_needed` more people) — D64, never a fixed floor."
                        },
                        "people_needed": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "confidence",
                        "people_needed"
                      ],
                      "description": "Bookings: how sure a scale is."
                    },
                    "cost_per_booking": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "expected_value": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "value_per_patient": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The Línea’s cash per evaluated new patient used (CLP)."
                    },
                    "judged_on": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "cash",
                        "expected"
                      ]
                    }
                  },
                  "required": [
                    "booking_rate",
                    "booking_interval",
                    "comparator",
                    "expected_bookings",
                    "pause",
                    "scale",
                    "cost_per_booking",
                    "expected_value",
                    "value_per_patient",
                    "judged_on"
                  ],
                  "description": "How sure Vitrina is about the ad and what its bookings are worth (D63/D64): bookings first, cash once the ad has 5 payments of its own."
                },
                "signals": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "frequency": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "days_active": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "hook_rate": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "% (null for an image ad)."
                    },
                    "thumb_stop_rate": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "% (null for an image ad)."
                    },
                    "ctr": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Link CTR, %."
                    }
                  },
                  "required": [
                    "frequency",
                    "days_active",
                    "hook_rate",
                    "thumb_stop_rate",
                    "ctr"
                  ],
                  "description": "D67: the creative’s own signals (the engine’s feature store) — context for the verdict, never part of it."
                },
                "explanation": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "story": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "enum": [
                        "after_click",
                        "filters_well",
                        "creative_first",
                        "tired"
                      ]
                    },
                    "text": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "One Spanish sentence."
                    }
                  },
                  "required": [
                    "story",
                    "text"
                  ],
                  "description": "D67: where the ad loses people, read from its signals against the account’s median."
                },
                "lead": {
                  "type": "object",
                  "properties": {
                    "metric": {
                      "type": "string",
                      "enum": [
                        "accepted",
                        "cash",
                        "expected"
                      ]
                    },
                    "value": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "metric",
                    "value"
                  ],
                  "description": "The metric that RANKED the ad — the accepted-quote return, or (with `cash_evidence.used_for_ranking`) the better of it and the cash return. A surface naming the winner displays exactly this metric."
                },
                "rank": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "1 = the best ad; null = not ranked (too little spend or people)."
                },
                "verdict": {
                  "type": "string",
                  "enum": [
                    "best",
                    "pays",
                    "refresh",
                    "pause",
                    "watch",
                    "thin"
                  ],
                  "description": "`best` rank 1 with a return on its `lead` metric (at most one row) · `pays` returns ≥ 1× · `refresh` returns ≥ 1× but wears out · `pause` meets every pause rule · `thin` too little data · `watch` the rest. Never a probability."
                },
                "patient_return": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/AdsPatientReturn"
                    },
                    {
                      "description": "The ad’s patients (the ad opened their thread; the engine credited their first payment) and what they paid in the window; `null` = unknown."
                    }
                  ]
                }
              },
              "required": [
                "ad_external_id",
                "ad_name",
                "display_name",
                "ad_set_name",
                "campaign_name",
                "campaign_external_id",
                "thumbnail_url",
                "video_external_id",
                "ads_manager_url",
                "spend",
                "people",
                "booked",
                "attended",
                "accepted",
                "cash",
                "fatigue",
                "ratios",
                "lead",
                "rank",
                "verdict"
              ]
            }
          },
          "best_ad_external_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "window",
          "coverage",
          "basis",
          "cash_evidence",
          "evidence",
          "rows",
          "best_ad_external_id"
        ]
      },
      "AdsByLinea": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "days": {
                "type": "integer"
              }
            },
            "required": [
              "from",
              "to",
              "days"
            ]
          },
          "coverage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsCoverage"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "lineas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "linea_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "`null` = «Sin línea»: ads no rule places."
                },
                "nombre": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ads": {
                  "type": "integer",
                  "description": "Ads of the window that belong here."
                },
                "spend": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Spend (CLP); null = unread."
                },
                "people": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "People whose first message came from these ads."
                },
                "booked": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "attended": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "accepted": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "count": {
                      "type": "number"
                    },
                    "value": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "count",
                    "value"
                  ],
                  "description": "Accepted presupuestos / closed sales — pipeline, never cash."
                },
                "cash": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "first": {
                      "type": "number"
                    },
                    "recurring": {
                      "type": "number"
                    },
                    "total": {
                      "type": "number"
                    },
                    "payments": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "first",
                    "recurring",
                    "total",
                    "payments"
                  ],
                  "description": "Attributed cash (CLP): first payment + later payments."
                },
                "ratios": {
                  "type": "object",
                  "properties": {
                    "accepted_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Accepted presupuestos / closed sales ÷ spend (×)."
                    },
                    "cash_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Cash (first + recurring payments) ÷ spend (×)."
                    },
                    "first_payment_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "First-payment cash ÷ spend (×)."
                    },
                    "cost_per_person": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The spend of the people window ÷ `people` (CLP) — the same division as a campaign’s «por persona»."
                    },
                    "expected_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The expected value of the ad’s bookings ÷ spend (×, D63): bookings × its Línea’s cash per evaluated new patient × the clinic’s show rate."
                    },
                    "cost_per_booking": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ bookings (CLP, «costo por cita agendada»)."
                    }
                  },
                  "required": [
                    "accepted_return",
                    "cash_return",
                    "first_payment_return",
                    "cost_per_person"
                  ]
                }
              },
              "required": [
                "linea_id",
                "nombre",
                "ads",
                "spend",
                "people",
                "booked",
                "attended",
                "accepted",
                "cash",
                "ratios"
              ]
            },
            "description": "Every live Línea (even with no ads), then «Sin línea» when it holds any."
          },
          "totals": {
            "type": "object",
            "properties": {
              "ads": {
                "type": "integer",
                "description": "Ads of the window that belong here."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Spend (CLP); null = unread."
              },
              "people": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "People whose first message came from these ads."
              },
              "booked": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "attended": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "accepted": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "count": {
                    "type": "number"
                  },
                  "value": {
                    "type": "number"
                  }
                },
                "required": [
                  "count",
                  "value"
                ],
                "description": "Accepted presupuestos / closed sales — pipeline, never cash."
              },
              "cash": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "first": {
                    "type": "number"
                  },
                  "recurring": {
                    "type": "number"
                  },
                  "total": {
                    "type": "number"
                  },
                  "payments": {
                    "type": "integer"
                  }
                },
                "required": [
                  "first",
                  "recurring",
                  "total",
                  "payments"
                ],
                "description": "Attributed cash (CLP): first payment + later payments."
              },
              "ratios": {
                "type": "object",
                "properties": {
                  "accepted_return": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Accepted presupuestos / closed sales ÷ spend (×)."
                  },
                  "cash_return": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Cash (first + recurring payments) ÷ spend (×)."
                  },
                  "first_payment_return": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "First-payment cash ÷ spend (×)."
                  },
                  "cost_per_person": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "The spend of the people window ÷ `people` (CLP) — the same division as a campaign’s «por persona»."
                  },
                  "expected_return": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "The expected value of the ad’s bookings ÷ spend (×, D63): bookings × its Línea’s cash per evaluated new patient × the clinic’s show rate."
                  },
                  "cost_per_booking": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Spend ÷ bookings (CLP, «costo por cita agendada»)."
                  }
                },
                "required": [
                  "accepted_return",
                  "cash_return",
                  "first_payment_return",
                  "cost_per_person"
                ]
              }
            },
            "required": [
              "ads",
              "spend",
              "people",
              "booked",
              "attended",
              "accepted",
              "cash",
              "ratios"
            ],
            "description": "The scorecard’s own totals: the Líneas plus «Sin línea» add up to it."
          },
          "plan": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "month_budget": {
                "type": "number",
                "description": "The account’s spend of the last 30 days (major units)."
              },
              "week_budget": {
                "type": "number"
              },
              "unallocated_month": {
                "type": "number",
                "description": "What no Línea can take this month (no hours, under 1×, capacity reached)."
              },
              "lineas": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "linea_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "nombre": {
                      "type": "string"
                    },
                    "spend": {
                      "type": "number"
                    },
                    "pace_month": {
                      "type": "number",
                      "description": "That spend as a month."
                    },
                    "booked": {
                      "type": "integer"
                    },
                    "cost_per_booking": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Its ads’ cash (with 5 own payments) or expected value ÷ spend."
                    },
                    "marginal_return": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The return of its best ad with evidence — what the next peso earns; orders the split."
                    },
                    "capacity": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "free_minutes_14": {
                          "type": "number"
                        },
                        "visit_minutes": {
                          "type": "number"
                        },
                        "next_slot_day": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "slots_month": {
                          "type": "integer"
                        },
                        "slots_week": {
                          "type": "integer"
                        },
                        "bottleneck": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "free_minutes_14",
                        "visit_minutes",
                        "next_slot_day",
                        "slots_month",
                        "slots_week",
                        "bottleneck"
                      ],
                      "description": "The agenda of the Línea’s professionals; `null` = unknown (never caps)."
                    },
                    "month": {
                      "type": "number",
                      "description": "Suggested budget for the month."
                    },
                    "week": {
                      "type": "number",
                      "description": "Suggested budget for this week (Monday)."
                    },
                    "reason": {
                      "type": "string",
                      "enum": [
                        "capacity",
                        "value",
                        "no_hours",
                        "low_return",
                        "no_data",
                        "no_ads"
                      ]
                    },
                    "reopens_on": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "A usable hour within 7 days: the «reanudar» moment."
                    }
                  },
                  "required": [
                    "linea_id",
                    "nombre",
                    "spend",
                    "pace_month",
                    "booked",
                    "cost_per_booking",
                    "return",
                    "marginal_return",
                    "capacity",
                    "month",
                    "week",
                    "reason",
                    "reopens_on"
                  ]
                }
              }
            },
            "required": [
              "month_budget",
              "week_budget",
              "unallocated_month",
              "lineas"
            ],
            "description": "D65 · the monthly and Monday split across Líneas against agenda capacity: best next peso first, each up to its current pace +20 % and never past what its free hours can take; a Línea with no hours gets $0."
          }
        },
        "required": [
          "window",
          "coverage",
          "lineas",
          "totals"
        ]
      },
      "AdsByProyecto": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "days": {
                "type": "integer"
              }
            },
            "required": [
              "from",
              "to",
              "days"
            ]
          },
          "coverage": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsCoverage"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "proyectos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "proyecto_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid",
                  "description": "`null` = «Sin proyecto»: ads no rule places."
                },
                "nombre": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "region": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "comuna": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "match": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "ad_id",
                    "name"
                  ],
                  "description": "How its ads were placed: `ad_id` when any ad is listed in the proyecto’s `meta_ad_ids`, else `name` (the proyecto’s name appears in the campaign / ad set / ad name). `null` = no ads."
                },
                "campaign_names": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Up to four campaign names, biggest spend first."
                },
                "ads": {
                  "type": "integer",
                  "description": "Ads of the window that belong here."
                },
                "campaigns": {
                  "type": "integer",
                  "description": "Distinct campaigns among those ads."
                },
                "spend": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Spend (CLP); null = unread."
                },
                "leads": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "People whose first contact came from these ads."
                },
                "booked": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Visitas agendadas."
                },
                "attended": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Visitas realizadas."
                },
                "reservas": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Reservas: the closing outcome the board records."
                },
                "reservas_value": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Sum of the reservas’ values (CLP)."
                },
                "costs": {
                  "type": "object",
                  "properties": {
                    "per_lead": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ leads (CLP)."
                    },
                    "per_booked": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ visitas agendadas."
                    },
                    "per_attended": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ visitas realizadas."
                    },
                    "per_reserva": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Spend ÷ reservas."
                    }
                  },
                  "required": [
                    "per_lead",
                    "per_booked",
                    "per_attended",
                    "per_reserva"
                  ]
                },
                "rates": {
                  "type": "object",
                  "properties": {
                    "lead_to_booked": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "booked_to_attended": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "attended_to_reserva": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "lead_to_booked",
                    "booked_to_attended",
                    "attended_to_reserva"
                  ]
                }
              },
              "required": [
                "proyecto_id",
                "nombre",
                "region",
                "comuna",
                "match",
                "campaign_names",
                "ads",
                "campaigns",
                "spend",
                "leads",
                "booked",
                "attended",
                "reservas",
                "reservas_value",
                "costs",
                "rates"
              ]
            },
            "description": "Every live proyecto (even with no ads), biggest spend first, then «Sin proyecto» when it holds any ad."
          },
          "totals": {
            "type": "object",
            "properties": {
              "ads": {
                "type": "integer",
                "description": "Ads of the window that belong here."
              },
              "campaigns": {
                "type": "integer",
                "description": "Distinct campaigns among those ads."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Spend (CLP); null = unread."
              },
              "leads": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "People whose first contact came from these ads."
              },
              "booked": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Visitas agendadas."
              },
              "attended": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Visitas realizadas."
              },
              "reservas": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Reservas: the closing outcome the board records."
              },
              "reservas_value": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Sum of the reservas’ values (CLP)."
              },
              "costs": {
                "type": "object",
                "properties": {
                  "per_lead": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Spend ÷ leads (CLP)."
                  },
                  "per_booked": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Spend ÷ visitas agendadas."
                  },
                  "per_attended": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Spend ÷ visitas realizadas."
                  },
                  "per_reserva": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Spend ÷ reservas."
                  }
                },
                "required": [
                  "per_lead",
                  "per_booked",
                  "per_attended",
                  "per_reserva"
                ]
              },
              "rates": {
                "type": "object",
                "properties": {
                  "lead_to_booked": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "booked_to_attended": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "attended_to_reserva": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                },
                "required": [
                  "lead_to_booked",
                  "booked_to_attended",
                  "attended_to_reserva"
                ]
              }
            },
            "required": [
              "ads",
              "campaigns",
              "spend",
              "leads",
              "booked",
              "attended",
              "reservas",
              "reservas_value",
              "costs",
              "rates"
            ],
            "description": "The scorecard’s own totals: the proyectos plus «Sin proyecto» add up to it."
          }
        },
        "required": [
          "window",
          "coverage",
          "proyectos",
          "totals"
        ]
      },
      "AdsPeopleFunnel": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "contacted",
                    "booked",
                    "attended",
                    "quote_presented",
                    "accepted",
                    "paid"
                  ]
                },
                "ads": {
                  "type": "integer",
                  "description": "People from ads: their first message came from an ad («llegaron») or an ad brought them back in the window («volvieron»)."
                },
                "ads_returned": {
                  "type": "integer",
                  "description": "Of `ads`, those an ad brought back («volvieron»)."
                },
                "other": {
                  "type": "integer"
                },
                "total": {
                  "type": "integer"
                },
                "conversion": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "total ÷ the previous step’s total (0–1)."
                }
              },
              "required": [
                "key",
                "ads",
                "other",
                "total",
                "conversion"
              ]
            }
          },
          "earlier": {
            "type": "object",
            "properties": {
              "people": {
                "type": "integer"
              },
              "accepted_count": {
                "type": "integer"
              },
              "accepted_value": {
                "type": "number"
              },
              "paid_value": {
                "type": "number"
              }
            },
            "required": [
              "people",
              "accepted_count",
              "accepted_value",
              "paid_value"
            ],
            "description": "People whose first contact came BEFORE the window: their accepted plans and payments in it — outside the funnel."
          },
          "credit_known": {
            "type": "boolean"
          }
        },
        "required": [
          "window",
          "steps",
          "earlier",
          "credit_known"
        ]
      },
      "AdsCohortRow": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}$",
            "description": "The acquisition month (America/Santiago): the month of each ad person’s first payment counted toward ad return."
          },
          "measured": {
            "type": "boolean",
            "description": "`false` = the month ended before measurement started: every figure is `null` («—»), never 0."
          },
          "spend": {
            "type": [
              "number",
              "null"
            ],
            "description": "Covered ad spend that month (CLP)."
          },
          "patients": {
            "type": [
              "integer",
              "null"
            ],
            "description": "New ad patients acquired that month."
          },
          "cost_per_new_patient": {
            "type": [
              "number",
              "null"
            ],
            "description": "`spend ÷ patients` (CLP)."
          },
          "m0": {
            "type": [
              "number",
              "null"
            ],
            "description": "Average paid per patient in the acquisition month (CLP); `null` = that month has not come yet, or no patients — show «—», never the to-date value."
          },
          "m1": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cumulative average paid per patient by month +1 (CLP); `null` = that month has not come yet, or no patients — show «—», never the to-date value."
          },
          "m2": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cumulative average paid per patient by month +2 (CLP); `null` = that month has not come yet, or no patients — show «—», never the to-date value."
          },
          "m3": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cumulative average paid per patient by month +3 (CLP); `null` = that month has not come yet, or no patients — show «—», never the to-date value."
          },
          "m5": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cumulative average paid per patient by month +5 (CLP); `null` = that month has not come yet, or no patients — show «—», never the to-date value."
          },
          "to_date": {
            "type": [
              "number",
              "null"
            ],
            "description": "Everything the cohort paid up to the window’s end, per patient (CLP)."
          }
        },
        "required": [
          "month",
          "measured",
          "spend",
          "patients",
          "cost_per_new_patient",
          "m0",
          "m1",
          "m2",
          "m3",
          "m5",
          "to_date"
        ]
      },
      "AdsCohorts": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "cohorts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsCohortRow"
            },
            "description": "One row per calendar month of the window, oldest first."
          },
          "comparison": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "ads": {
                "type": "object",
                "properties": {
                  "patients": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "avg_paid": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Paid in the window per patient (CLP)."
                  },
                  "avg_payments": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Payments in the window per patient (one decimal)."
                  }
                },
                "required": [
                  "patients",
                  "avg_paid",
                  "avg_payments"
                ]
              },
              "other": {
                "type": "object",
                "properties": {
                  "patients": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "avg_paid": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Paid in the window per patient (CLP)."
                  },
                  "avg_payments": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Payments in the window per patient (one decimal)."
                  }
                },
                "required": [
                  "patients",
                  "avg_paid",
                  "avg_payments"
                ]
              }
            },
            "required": [
              "ads",
              "other"
            ],
            "description": "The patients who paid in the (covered) window, in two DISJOINT groups: those with at least one payment the ad rule counts (measured on those payments) vs every other patient. Each patient is in one group, so `ads.patients + other.patients` is the distinct payers. `null` = unknown."
          },
          "days_to_first_payment": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "median": {
                "type": "number"
              },
              "p25": {
                "type": "number"
              },
              "p75": {
                "type": "number"
              },
              "patients": {
                "type": "integer"
              }
            },
            "required": [
              "median",
              "p25",
              "p75",
              "patients"
            ],
            "description": "Whole days from the credited ad click to the first payment, over the ad patients whose first payment falls in the window."
          },
          "pipeline_uncollected": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "count": {
                "type": "integer",
                "minimum": 0
              },
              "value": {
                "type": "number",
                "minimum": 0
              }
            },
            "required": [
              "count",
              "value"
            ],
            "description": "Accepted presupuestos / closed sales credited to an ad in the window whose person has not paid anything yet («lo que viene»): how many and their recorded value (CLP). `null` = unknown."
          },
          "joins_state": {
            "type": "string",
            "enum": [
              "ready",
              "pending",
              "unavailable"
            ],
            "description": "Why the figures are what they are (the `/ads/overview` vocabulary): `pending` = the attribution credit is being read — show «Calculando…» and read again in a few seconds; `unavailable` = the read failed — «—»."
          },
          "coverage": {
            "$ref": "#/components/schemas/AdsCoverage"
          }
        },
        "required": [
          "window",
          "cohorts",
          "comparison",
          "days_to_first_payment",
          "pipeline_uncollected",
          "joins_state",
          "coverage"
        ]
      },
      "AdsMonthlyReturnPoint": {
        "type": "object",
        "properties": {
          "month": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}$",
            "description": "The America/Santiago calendar month, `YYYY-MM`."
          },
          "from": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The month’s first day inside the window."
          },
          "to": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The month’s last day inside the window."
          },
          "measured": {
            "type": "boolean",
            "description": "`false` when the month ended before measurement started (`coverage.measured_from`): every figure is `null` — show «—», never 0."
          },
          "counted_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The first day counted: the later of `from` and the measurement start. `null` when the month is not measured."
          },
          "partial": {
            "type": "boolean",
            "description": "`true` when the counted days are not the whole calendar month (the window or the measurement start cuts it, or it is still running)."
          },
          "spend": {
            "type": [
              "number",
              "null"
            ],
            "description": "Ad spend over the counted days (CLP); `null` = unknown — also whenever `spend_unknown_days` is not empty: show «—», never a lower total."
          },
          "spend_unknown_days": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "description": "Counted days whose spend the engine did not answer, oldest first (empty = the month’s spend is complete). Name them beside the «—» (e.g. «Falta el gasto del 30 sep»)."
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "What the ad patients paid in the month (CLP): `first_value` + `later_value`. The measured months add up to `total.value`."
          },
          "first_value": {
            "type": [
              "number",
              "null"
            ],
            "description": "Of `value`, each person’s first payment."
          },
          "later_value": {
            "type": [
              "number",
              "null"
            ],
            "description": "Of `value`, the later payments (cuotas, more treatment)."
          },
          "patients": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Ad patients with at least one payment in the month."
          },
          "payments": {
            "type": [
              "integer",
              "null"
            ]
          },
          "roas": {
            "type": [
              "number",
              "null"
            ],
            "description": "`value ÷ spend`; `null` when either is unknown or spend is 0."
          }
        },
        "required": [
          "month",
          "from",
          "to",
          "measured",
          "counted_from",
          "partial",
          "spend",
          "spend_unknown_days",
          "value",
          "first_value",
          "later_value",
          "patients",
          "payments",
          "roas"
        ]
      },
      "AdsMonthlyReturn": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "months": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsMonthlyReturnPoint"
            },
            "description": "Every calendar month the window touches, oldest first."
          },
          "total": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "`null` when any measured month’s spend is unknown (`spend_unknown_days`) — never a sum short of a day."
              },
              "value": {
                "type": "number"
              },
              "first_value": {
                "type": "number"
              },
              "later_value": {
                "type": "number"
              },
              "patients": {
                "type": "integer",
                "description": "Distinct ad patients who paid in the window."
              },
              "payments": {
                "type": "integer"
              },
              "roas": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "spend",
              "value",
              "first_value",
              "later_value",
              "patients",
              "payments",
              "roas"
            ],
            "description": "The measured months together — the same figures as `/ads/overview`’s `current.patient_return` for the window. `null` = unknown (the credit is still being read, or the measurement start is unknown): show «—»."
          },
          "coverage": {
            "$ref": "#/components/schemas/AdsCoverage"
          },
          "payments_connected": {
            "type": "boolean",
            "description": "The workspace’s payments reach Vitrina (a clinic ledger or the synced mirror). `false` = no payments rail at all: every payment figure is `null` — say why («tus pagos no llegan a Vitrina»), never $0."
          }
        },
        "required": [
          "window",
          "months",
          "total",
          "coverage",
          "payments_connected"
        ]
      },
      "AdsStages": {
        "type": "object",
        "properties": {
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "m1": {
            "type": "object",
            "properties": {
              "conversations": {
                "type": "integer",
                "minimum": 0,
                "description": "People who WROTE from ads (first contact in the window) — `/ads/funnel` `contacted.ads`."
              },
              "spend": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Covered ad spend over the window; `null` = unknown."
              },
              "cost_per_conversation": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "conversations",
              "spend",
              "cost_per_conversation"
            ],
            "description": "M1 · Anuncios (Meta / the agency)."
          },
          "m2": {
            "type": "object",
            "properties": {
              "ad_conversations": {
                "type": "integer",
                "minimum": 0,
                "description": "PEOPLE of the M1 cohort (their first message in the window came from an ad — `m1.conversations`), the base of `answered`; never more than M1."
              },
              "answered": {
                "type": "integer",
                "minimum": 0,
                "description": "Of those people, the ones who saw an answer after their first message (the team, the AI or an integration)."
              },
              "answered_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1,
                "description": "A share of COUNTS (0–1); `null` when its base is 0."
              },
              "median_first_response_minutes": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Median minutes from the person’s first message to the first answer."
              },
              "booked": {
                "type": "integer",
                "minimum": 0
              },
              "attended": {
                "type": "integer",
                "minimum": 0
              },
              "booked_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1,
                "description": "booked ÷ m1.conversations."
              },
              "attended_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1,
                "description": "attended ÷ booked."
              },
              "booking_link": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "bookings": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "New agenda bookings created in the window (an import of past appointments and professional blocks excluded)."
                  },
                  "linked": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Of them, the ones that name a person (a contact)."
                  }
                },
                "required": [
                  "bookings",
                  "linked"
                ],
                "description": "How many of the window’s bookings a person’s funnel can see. `null` = not read."
              },
              "booked_measured": {
                "type": "boolean",
                "description": "`false` = most of the window’s bookings name nobody (at least 10 bookings, under half linked): `booked` undercounts — an identity-link gap, not a conversion — so M2 gets no verdict (`weakest` is never `m2`) and the band is not applied."
              },
              "rate_not_comparable": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "conversations_late",
                  "booking_link"
                ],
                "description": "Why `booked_rate` may NOT be compared with the band nor projected into a budget («¿Cuánto invertir?»): `conversations_late` = conversations are recorded only after the window starts (most people wrote before); `booking_link` = most bookings name nobody (`booked_measured` false). `null` = comparable."
              },
              "new_people": {
                "type": "object",
                "properties": {
                  "window": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "from": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "to": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      }
                    },
                    "required": [
                      "from",
                      "to"
                    ],
                    "description": "The days counted: the period from `booking_start`, without the last `mature_days` days (a person who wrote yesterday has had no time to book). `null` = no such day (`unmeasured`)."
                  },
                  "booking_start": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "The first day the assistant answered people (at least 5 on one day), derived from the messages. `null` = it never answered anyone: the window then starts with the conversations Vitrina measures."
                  },
                  "mature_days": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "unmeasured": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "before_booking_start",
                      "too_recent"
                    ],
                    "description": "`before_booking_start` = the period ends before the assistant started; `too_recent` = all its days since then are within the last `mature_days`. `null` = measured."
                  },
                  "people": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "NEW people whose first message came from an ad and fell in `window` (never «volvieron»)."
                  },
                  "booked": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Of `people`, with a REAL cita booked since their first message (D91: booked by Vitrina, by reception or mirrored from the clinic software; anuladas out). A quote or a payment with no cita is not «agendó»."
                  },
                  "booked_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1,
                    "description": "booked ÷ people — THE number held against the 10–15 % band."
                  },
                  "paid": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Of `booked`, those who paid."
                  },
                  "paid_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1,
                    "description": "paid ÷ booked."
                  },
                  "replied": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "«Respondieron»: of `people`, who wrote again after the first answer they saw (within 7 days of their first message)."
                  },
                  "replied_booked": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Of `replied`, with a real cita."
                  },
                  "replied_rate": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1,
                    "description": "replied_booked ÷ replied."
                  },
                  "returned": {
                    "type": "object",
                    "properties": {
                      "people": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "booked": {
                        "type": "integer",
                        "minimum": 0
                      }
                    },
                    "required": [
                      "people",
                      "booked"
                    ],
                    "description": "«Volvieron»: people an ad brought BACK in `window` (they had written before) and, of them, with a real cita since — their own line, never in the rate."
                  }
                },
                "required": [
                  "window",
                  "booking_start",
                  "mature_days",
                  "unmeasured",
                  "people",
                  "booked",
                  "booked_rate",
                  "paid",
                  "paid_rate",
                  "replied",
                  "replied_booked",
                  "replied_rate",
                  "returned"
                ],
                "description": "D93 — M2’s own cohort, the one its verdict (`weakest`) is judged on. Unlike `booked` (the `/ads/funnel` cohort: every person from ads in the period, «volvieron» included, any step reached), it counts new people only, from the booking start, mature days only, real citas only. Absent when it could not be read."
              }
            },
            "required": [
              "ad_conversations",
              "answered",
              "answered_rate",
              "median_first_response_minutes",
              "booked",
              "attended",
              "booked_rate",
              "attended_rate",
              "booking_link",
              "booked_measured",
              "rate_not_comparable"
            ],
            "description": "M2 · Agenda (the front desk / the agent)."
          },
          "m3": {
            "type": "object",
            "properties": {
              "quote_presented": {
                "type": "integer",
                "minimum": 0
              },
              "accepted": {
                "type": "integer",
                "minimum": 0
              },
              "paid": {
                "type": "integer",
                "minimum": 0
              },
              "paid_rate": {
                "type": [
                  "number",
                  "null"
                ],
                "minimum": 0,
                "maximum": 1,
                "description": "paid ÷ quote_presented."
              }
            },
            "required": [
              "quote_presented",
              "accepted",
              "paid",
              "paid_rate"
            ],
            "description": "M3 · Venta en clínica (the professional). The same people as `/ads/funnel`."
          },
          "budget_cohorts": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "weeks": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "week": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                      "description": "The week’s Monday (America/Santiago)."
                    },
                    "week_end": {
                      "type": "string",
                      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                      "description": "The week’s Sunday."
                    },
                    "complete": {
                      "type": "boolean",
                      "description": "`false` = the window cuts the week or it is still running: its counts are partial, never to be set beside a whole week."
                    },
                    "presented": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Budgets PRESENTED in the week (`quote_presented` events dated in it) — the same weekly count «Envío a Meta» shows."
                    },
                    "accepted": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Budgets ACCEPTED in the week (`closed_won` events dated in it). It can exceed `presented`: an acceptance belongs to the week its budget was presented, not to the week it closed. `accepted` = `accepted_same_week` + `accepted_earlier_week` + `accepted_no_presentation` + `accepted_without_budget`."
                    },
                    "accepted_same_week": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Of `accepted`, presented in the same week (or whose presentation is dated after the acceptance)."
                    },
                    "accepted_earlier_week": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Of `accepted`, presented in an EARLIER week."
                    },
                    "accepted_no_presentation": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Of `accepted`, a budget with no presentation event recorded (accepted without being marked as presented, or presented before the events were recorded)."
                    },
                    "accepted_without_budget": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Of `accepted`, closed through a deal — no budget behind it."
                    },
                    "cohort_accepted_to_date": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "The cohort view: of the `presented` budgets of THIS week, how many have been accepted so far (any later week included)."
                    },
                    "cohort_accepted_same_week": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Of the cohort, accepted within the presentation week."
                    }
                  },
                  "required": [
                    "week",
                    "week_end",
                    "complete",
                    "presented",
                    "accepted",
                    "accepted_same_week",
                    "accepted_earlier_week",
                    "accepted_no_presentation",
                    "accepted_without_budget",
                    "cohort_accepted_to_date",
                    "cohort_accepted_same_week"
                  ]
                }
              },
              "totals": {
                "type": "object",
                "properties": {
                  "presented": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "accepted": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "accepted_same_week": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "accepted_earlier_week": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "accepted_no_presentation": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "accepted_without_budget": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "cohort_accepted_to_date": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "cohort_accepted_same_week": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "required": [
                  "presented",
                  "accepted",
                  "accepted_same_week",
                  "accepted_earlier_week",
                  "accepted_no_presentation",
                  "accepted_without_budget",
                  "cohort_accepted_to_date",
                  "cohort_accepted_same_week"
                ]
              }
            },
            "required": [
              "weeks",
              "totals"
            ],
            "description": "M3’s budget funnel by presentation cohort, week by week (Monday–Sunday, America/Santiago, over the window). Counts by the day each event HAPPENED, from the same outcome rows as «Envío a Meta»’s «por semana» (`/ads/meta-send`), so the totals reconcile with those weekly volumes; this is a clinic-wide count (every origin), not only ad people, unlike `m3`. It explains «accepted > presented» in a week: see `accepted_earlier_week`. `null` = not read; absent on the sample."
          },
          "reference": {
            "type": "object",
            "properties": {
              "m2_booked_rate": {
                "type": "object",
                "properties": {
                  "low": {
                    "type": "number"
                  },
                  "high": {
                    "type": "number"
                  },
                  "label": {
                    "type": "string",
                    "enum": [
                      "program"
                    ],
                    "description": "«Rango de referencia del programa»."
                  }
                },
                "required": [
                  "low",
                  "high",
                  "label"
                ]
              }
            },
            "required": [
              "m2_booked_rate"
            ]
          },
          "weakest": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "m1",
              "m2"
            ],
            "description": "Where it leaks, deterministically and only where a benchmark exists: `m1` = spend and nobody wrote (no credited person from ads AND no ad-opened conversation in `m2.ad_conversations`); `m2` = writing → booking (`m2.new_people.booked_rate` when present) under the program reference low (10 %) while `m2.booked_measured`; `null` = neither, and always when Vitrina does not measure conversations from the window’s first day. M3 is never judged: there is no program benchmark for quotes → payment, so `m3.paid_rate` is shown without a verdict."
          },
          "credit_known": {
            "type": "boolean",
            "description": "`false` when the attribution credit could not be read: «from ads» rests on Vitrina’s ad touches only."
          }
        },
        "required": [
          "window",
          "m1",
          "m2",
          "m3",
          "reference",
          "weakest",
          "credit_known"
        ]
      },
      "AdsSignalComponent": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "enum": [
              "meta",
              "origin",
              "site_tag"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "partial",
              "missing",
              "skipped",
              "unknown"
            ]
          },
          "earned": {
            "type": "number",
            "description": "Points earned; 0 when `skipped` or `unknown`."
          },
          "max": {
            "type": "number",
            "description": "The component’s weight (meta 30, origin 40, site_tag 30); it counts toward the total unless the status is `skipped` or `unknown`."
          }
        },
        "required": [
          "key",
          "status",
          "earned",
          "max"
        ]
      },
      "AdsSignalsScore": {
        "type": "object",
        "properties": {
          "score": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 100,
            "description": "Salud’s «Puntaje de señales», 0–100: the earned share of the components that could be judged. `null` when none could."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsSignalComponent"
            }
          }
        },
        "required": [
          "score",
          "components"
        ],
        "description": "Vitrina’s own signal score with partial credit: the Meta connection, the share of results with their origin identified (proportional — the same population as `outcomes_coverage`) and the website tag (live full, sparse partial, never reported 0; skipped when the workspace has no website). Not the engine’s `trust.instrumentation.score`, which grades a web checkout only."
      },
      "AdsOrigins": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "people_total": {
            "type": "integer",
            "minimum": 0,
            "description": "Distinct people with a recorded outcome in the window."
          },
          "buckets": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "ads",
                    "ads_google",
                    "ads_returned",
                    "ads_stale",
                    "whatsapp_organic",
                    "instagram_organic",
                    "messenger_organic",
                    "website",
                    "in_person",
                    "before_measuring",
                    "unknown"
                  ],
                  "description": "`ads` an outcome the engine credited to an ad in the window, or an ad touch (click-to-WhatsApp, an Instagram / Messenger DM ad, an imported ad touch) within 90 days before the person’s first outcome; `ads_google` the same, from a Google Ads campaign (shown when the account runs one); `ads_stale` an ad touch, but only more than 90 days before it (known, not credited, never a measurement problem); `*_organic` the first conversation on that channel, no ad; `website` a booking on the public booking page or widget, or a form / booking a connected CRM recorded, before any conversation and not (yet) credited to an ad; `in_person` an appointment with no conversation or website booking before it (walk-in / phone); `before_measuring` first contact before the measurement start; `unknown` nothing."
                },
                "people": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "required": [
                "key",
                "people"
              ]
            },
            "description": "Every key, always in this order; each person in ONE."
          }
        },
        "required": [
          "people_total",
          "buckets"
        ],
        "description": "«Resultados por origen» in PEOPLE over the coverage window. `null` = unknown (the read failed)."
      },
      "AdsSite": {
        "type": "object",
        "properties": {
          "relevant": {
            "type": "boolean",
            "description": "Whether the account’s ads send people to the website in the window (site-destination spend or site sessions). `false` = the website tag is no measurement problem and no action."
          },
          "site_spend_share": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1,
            "description": "Share (0–1) of the window’s spend on website-destination campaigns (traffic / sales objectives); `null` = unknown."
          },
          "sessions": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Site visits the tag saw in the window; `null` = unknown."
          }
        },
        "required": [
          "relevant",
          "site_spend_share",
          "sessions"
        ]
      },
      "AdsHealth": {
        "type": "object",
        "properties": {
          "trust": {
            "type": "object",
            "properties": {
              "model": {
                "type": "string"
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "traceable_pct_by_count": {
                "type": "number"
              },
              "traceable_pct_by_value": {
                "type": "number"
              },
              "total_count": {
                "type": "integer"
              },
              "total_value": {
                "type": "number"
              },
              "cash_source": {
                "type": "string",
                "enum": [
                  "clinic_ledger",
                  "engine"
                ],
                "description": "`clinic_ledger` = `total_*` and the `traceable_pct_*` shares divide by everything the clinic software collected in the window (the one payment source while it is connected); absent = the attribution totals."
              },
              "instrumentation": {
                "type": "object",
                "properties": {
                  "score": {
                    "type": "number"
                  },
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "healthy_tracking",
                      "broken_tracking"
                    ],
                    "description": "Vitrina’s verdict: `broken_tracking` only when too many people have no origin after measurement started (the same verdict as `measurement_problem`); the engine’s web-checkout grade only when the people read failed."
                  },
                  "tracking_healthy": {
                    "type": "boolean"
                  },
                  "coverage_interpretation": {
                    "type": "string"
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Signals still missing. Never `payment_provider` while payments reach Vitrina (`coverage.payments_connected`)."
                  },
                  "tracker": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "live",
                          "sparse",
                          "missing"
                        ],
                        "description": "`live` = 10 or more web-tag touches in the window and one in the last 7 days; `sparse` = at least one touch but not live (few visits, or none in the last 7 days); `missing` = no touch at all. `missing[]` lists `tracker` for both `sparse` and `missing` — only `missing` means the tag sent nothing."
                      },
                      "web_touches": {
                        "type": "integer"
                      },
                      "last_web_touch": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "fresh": {
                        "type": "boolean",
                        "description": "A web-tag touch inside the last 7 days."
                      }
                    },
                    "required": [
                      "status",
                      "web_touches",
                      "last_web_touch",
                      "fresh"
                    ],
                    "description": "The website tag signal behind `missing[tracker]`. `null` when the engine did not report it (unknown, never «silent»)."
                  }
                },
                "required": [
                  "score",
                  "verdict",
                  "tracking_healthy",
                  "coverage_interpretation",
                  "missing",
                  "tracker"
                ]
              },
              "traceable_roas_trend": {
                "type": "object",
                "properties": {
                  "recent_roas": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "prior_roas": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "recent_spend": {
                    "type": "number"
                  },
                  "recent_traceable_revenue": {
                    "type": "number"
                  },
                  "days_with_cash": {
                    "type": "integer"
                  },
                  "prior_measured": {
                    "type": "boolean",
                    "description": "`false` when the prior half-window started before the measurement start (`coverage.measured_from`): `prior_roas` is no comparison, show «—»."
                  }
                },
                "required": [
                  "recent_roas",
                  "prior_roas",
                  "recent_spend",
                  "recent_traceable_revenue",
                  "days_with_cash",
                  "prior_measured"
                ]
              }
            },
            "required": [
              "model",
              "currency",
              "traceable_pct_by_count",
              "traceable_pct_by_value",
              "total_count",
              "total_value",
              "instrumentation",
              "traceable_roas_trend"
            ]
          },
          "utm": {
            "type": "object",
            "properties": {
              "scan_window_days": {
                "type": "integer"
              },
              "events_scanned": {
                "type": "integer"
              },
              "high_count": {
                "type": "integer"
              },
              "medium_count": {
                "type": "integer"
              },
              "refreshed_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "dangling_ad_ids": {
                "type": "object",
                "properties": {
                  "window_days": {
                    "type": "integer"
                  },
                  "model": {
                    "type": "string"
                  },
                  "dangling_conversions": {
                    "type": "integer"
                  },
                  "attributed_cash": {
                    "type": "number"
                  },
                  "reporting_currency": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "distinct_ad_ids": {
                    "type": "integer"
                  }
                },
                "required": [
                  "window_days",
                  "model",
                  "dangling_conversions",
                  "attributed_cash",
                  "reporting_currency",
                  "distinct_ad_ids"
                ]
              }
            },
            "required": [
              "scan_window_days",
              "events_scanned",
              "high_count",
              "medium_count",
              "refreshed_at",
              "dangling_ad_ids"
            ]
          },
          "attribution_coverage": {
            "type": "object",
            "properties": {
              "total_conversions": {
                "type": "integer"
              },
              "with_full_utms": {
                "type": "integer"
              },
              "with_fbclid_only": {
                "type": "integer"
              },
              "with_no_tracking": {
                "type": "integer"
              },
              "coverage_percent": {
                "type": "number"
              }
            },
            "required": [
              "total_conversions",
              "with_full_utms",
              "with_fbclid_only",
              "with_no_tracking",
              "coverage_percent"
            ]
          },
          "outcomes_coverage": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "outcomes": {
                "type": "integer"
              },
              "attributed_outcomes": {
                "type": "integer"
              },
              "coverage_percent": {
                "type": "number"
              }
            },
            "required": [
              "outcomes",
              "attributed_outcomes",
              "coverage_percent"
            ],
            "description": "THE population «cobertura» and «outcomes» share: the same counts `/ads/overview` answers for this window and model (`current.outcomes`, `current.attributed_outcomes`, `current.coverage_percent`). Salud prints this; `attribution_coverage` is the tracking-signal diagnostic (the engine’s attribution-eligible conversions and whether each carried a UTM / click id) — a different population, never shown as «de N». `null` when the overview read failed (the rest of the page stands)."
          },
          "signals": {
            "$ref": "#/components/schemas/AdsSignalsScore"
          },
          "coverage": {
            "$ref": "#/components/schemas/AdsCoverage"
          },
          "origins": {
            "$ref": "#/components/schemas/AdsOrigins"
          },
          "people_window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ],
            "description": "The days `origins` count over: the period clipped to the first recorded conversation — the same people window as `/ads/overview` `current.people_window`."
          },
          "site": {
            "$ref": "#/components/schemas/AdsSite"
          },
          "measurement_problem": {
            "type": "boolean",
            "description": "«La medición tiene problemas»: more than 40 % of the people measured after the measurement start (at least 10) have no origin at all. Organic channels, walk-ins and «antes de medir» are healthy."
          }
        },
        "required": [
          "trust",
          "utm",
          "attribution_coverage",
          "outcomes_coverage",
          "signals",
          "coverage",
          "origins",
          "site",
          "measurement_problem"
        ]
      },
      "AdsFact": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Stable fact key (`roas`, `best_campaign_name`, …) — a vocabulary name, not a resource id."
          },
          "label": {
            "type": "string",
            "description": "Spanish label in the workspace’s vocabulary."
          },
          "value": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "Raw value; `null` = unknown (never zero). `pct` values are 0–100."
          },
          "display": {
            "type": "string",
            "description": "The exact es-CL string a sentence quotes (`3,7×`, `$845.200`, `—`)."
          },
          "format": {
            "type": "string",
            "enum": [
              "clp",
              "int",
              "times",
              "pct",
              "text",
              "date",
              "days"
            ]
          },
          "source": {
            "type": "string",
            "description": "Where the value was read."
          },
          "threshold": {
            "type": "string",
            "description": "Present only on a fact that rests on a rule: the rule itself, in the owner’s words (for the wear verdict: «CTR 30 % o más bajo, o costo por resultado 43 % o más alto, durante 3 días seguidos frente a sus 28 días anteriores»). Nothing in a fact is a probability."
          },
          "privacy": {
            "type": "string",
            "enum": [
              "aggregate"
            ],
            "description": "Facts never carry a person."
          }
        },
        "required": [
          "key",
          "label",
          "value",
          "display",
          "format",
          "source",
          "privacy"
        ]
      },
      "AdsBriefing": {
        "type": "object",
        "properties": {
          "screen": {
            "type": "string",
            "enum": [
              "resumen",
              "campanas",
              "atribuidos",
              "creativos",
              "salud"
            ]
          },
          "window": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "window": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ]
          },
          "lead": {
            "type": "object",
            "properties": {
              "text": {
                "type": "string",
                "description": "One sentence, plain text; `**bold**` is the only markup."
              },
              "fact_keys": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "The facts this sentence quotes."
              },
              "ask_key": {
                "type": "string",
                "enum": [
                  "best_campaign",
                  "goal_pace",
                  "cash_vs_value",
                  "wearing_ads",
                  "cheapest_outcome",
                  "worst_spend",
                  "top_ad_closings",
                  "time_to_close",
                  "unmatched_today",
                  "why_best",
                  "what_if_pause",
                  "missing_signals",
                  "tag_impact",
                  "traceable_cash"
                ],
                "description": "The «Pregúntale» question this line invites, by recipe key (select the question by this, never by its label). Derived from the facts the line quotes. Absent when the line invites none; each key appears at most once per briefing."
              },
              "ask": {
                "type": "string",
                "description": "That question’s label in the workspace’s words (present exactly when `ask_key` is)."
              },
              "figure": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "fact_key": {
                    "type": "string"
                  },
                  "value": {
                    "anyOf": [
                      {
                        "type": "number"
                      },
                      {
                        "type": "string"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "clp",
                      "int",
                      "times",
                      "pct",
                      "text",
                      "date",
                      "days"
                    ]
                  },
                  "display": {
                    "type": "string",
                    "description": "The substring of `lead.text` to count up."
                  },
                  "offset": {
                    "type": "integer",
                    "description": "Where that whole figure starts in `lead.text` — split there, not on the first occurrence of `display` (a `12` can also sit inside `$12.000`)."
                  }
                },
                "required": [
                  "fact_key",
                  "value",
                  "format",
                  "display",
                  "offset"
                ]
              }
            },
            "required": [
              "text",
              "fact_keys",
              "figure"
            ]
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string",
                  "description": "One sentence, plain text; `**bold**` is the only markup."
                },
                "fact_keys": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "The facts this sentence quotes."
                },
                "ask_key": {
                  "type": "string",
                  "enum": [
                    "best_campaign",
                    "goal_pace",
                    "cash_vs_value",
                    "wearing_ads",
                    "cheapest_outcome",
                    "worst_spend",
                    "top_ad_closings",
                    "time_to_close",
                    "unmatched_today",
                    "why_best",
                    "what_if_pause",
                    "missing_signals",
                    "tag_impact",
                    "traceable_cash"
                  ],
                  "description": "The «Pregúntale» question this line invites, by recipe key (select the question by this, never by its label). Derived from the facts the line quotes. Absent when the line invites none; each key appears at most once per briefing."
                },
                "ask": {
                  "type": "string",
                  "description": "That question’s label in the workspace’s words (present exactly when `ask_key` is)."
                }
              },
              "required": [
                "text",
                "fact_keys"
              ]
            }
          },
          "facts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsFact"
            }
          },
          "actions": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Ids of the actions the rail should surface first (may be empty)."
          },
          "source": {
            "type": "string",
            "enum": [
              "model",
              "template",
              "facts"
            ],
            "description": "Who wrote the sentences — all from the same pinned facts. `model`: the language model (cached for these facts). `template`: built from the facts where the model is never used (first week, Meta disconnected, no spend reading, nothing to report, sample mode). `facts`: built from the facts because no model briefing was cached yet — one is being written in the background, so a later read returns `model`."
          },
          "generated_at": {
            "type": "string"
          },
          "vocabulary": {
            "type": "string",
            "enum": [
              "automotive",
              "healthcare",
              "real-estate",
              "generic"
            ]
          },
          "sample": {
            "type": "boolean",
            "description": "`true` when written from the sample dataset."
          }
        },
        "required": [
          "screen",
          "window",
          "lead",
          "lines",
          "facts",
          "actions",
          "source",
          "generated_at",
          "vocabulary",
          "sample"
        ]
      },
      "AdsGoal": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "outcomes",
              "value",
              "roas"
            ]
          },
          "target": {
            "type": "number"
          },
          "period": {
            "type": "string",
            "enum": [
              "month"
            ]
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "Who last set it (a member or API key id); `null` when unknown."
          },
          "persisted": {
            "type": "boolean",
            "description": "`false` in sample mode: the goal was validated but not stored."
          }
        },
        "required": [
          "kind",
          "target",
          "period",
          "updated_at",
          "updated_by"
        ]
      },
      "AdsShareResumen": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "month": {
                "type": "string",
                "description": "`YYYY-MM`."
              },
              "from": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "to": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "closed": {
                "type": "boolean",
                "description": "`false` for the running month (figures still growing)."
              },
              "label": {
                "type": "string",
                "description": "The period line printed on the card, «Agosto · 1 – 31 ago 2026»."
              }
            },
            "required": [
              "month",
              "from",
              "to",
              "closed",
              "label"
            ]
          },
          "vocabulary": {
            "type": "string",
            "enum": [
              "automotive",
              "healthcare",
              "real-estate",
              "generic"
            ],
            "description": "The noun family every label and sentence was written in."
          },
          "has_activity": {
            "type": "boolean",
            "description": "`true` when the month had spend or ad-attributed outcomes. `false` = nothing to report (the monthly email is not sent for such a month)."
          },
          "dominant": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "roas",
              "outcomes",
              "spend"
            ],
            "description": "The figure the card leads with: the return when the month has value, else the outcomes, else the spend. `null` without activity."
          },
          "headline": {
            "type": "string",
            "description": "The dominant figure as a title, month first («Agosto: 3,7× de retorno»)."
          },
          "tile": {
            "type": "object",
            "properties": {
              "number": {
                "type": "string",
                "description": "Display number («3,7×»)."
              },
              "unit": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Its unit («retorno»)."
              },
              "caption": {
                "type": "string"
              },
              "lines": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "number",
              "unit",
              "caption",
              "lines"
            ],
            "description": "The card as drawn, pre-formatted es-CL, in the tenant’s nouns."
          },
          "sentences": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Up to three fact-built sentences (no model) — the value, count, best campaign and change the image does not carry. `**x**` marks the lead figure."
          },
          "tracking_issue": {
            "type": [
              "string",
              "null"
            ],
            "description": "Set when the engine’s measurement verdict for the trailing 30 days (on the 1st–3rd, roughly the month just closed) is broken — the reason in plain words («el tag de tu sitio no envió datos»), printed after «Falta parte del seguimiento:». Never from today’s Meta or tag state. `null` when measurement was sound or could not be read."
          },
          "image_alt": {
            "type": "string",
            "description": "Alt text for the card image."
          },
          "figures": {
            "type": "object",
            "properties": {
              "spend": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "revenue": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "roas": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "attributed_outcomes": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "best_campaign": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "spend",
              "revenue",
              "roas",
              "attributed_outcomes",
              "best_campaign"
            ]
          }
        },
        "required": [
          "period",
          "vocabulary",
          "has_activity",
          "dominant",
          "headline",
          "tile",
          "sentences",
          "tracking_issue",
          "image_alt",
          "figures"
        ]
      },
      "AdsAction": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "`<kind>:<target external id>:<YYYY-MM-DD>` — a composite key, stable for a day."
          },
          "kind": {
            "type": "string",
            "enum": [
              "pause_ad",
              "campaign_pause",
              "adset_pause",
              "ad_resume",
              "budget_change",
              "adset_duplicate_swap",
              "utm_refresh",
              "wiring_recheck",
              "url_tags_fix",
              "send_setup",
              "send_stage",
              "send_dataset",
              "send_stop",
              "send_protected_move",
              "nav"
            ]
          },
          "screen": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "resumen",
                "campanas",
                "creativos",
                "salud",
                "envio"
              ]
            }
          },
          "primary": {
            "type": "boolean",
            "description": "The one card the rail leads with."
          },
          "title": {
            "type": "string"
          },
          "why": {
            "type": "string",
            "description": "Spanish; `**bold**` is the only markup."
          },
          "effect": {
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string",
                "enum": [
                  "ad",
                  "ad_set",
                  "campaign",
                  "profile"
                ]
              },
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Meta id of the object (null for profile-level actions)."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "campaign_external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Legacy: set only on a budget change inside the best campaign. Read `campaign.external_id` instead."
              },
              "campaign": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "The campaign’s Meta id."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "external_id",
                  "name"
                ],
                "description": "The campaign the card names — on an ad-set budget change, an ad pause and the navigation cards, whenever it is known."
              },
              "ad_set": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The ad set’s Meta id; `null` on an ad-level card (the reads carry the ad set by name only)."
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "external_id",
                  "name"
                ],
                "description": "The ad set the card names. Every ad-level card (pause, refresh the creative, prepare a variant, use as base) carries `campaign`, `ad_set` and the ad (`name`), so two ads with the same name in different ad sets tell apart; the title ends «… del conjunto «{ad_set}»»."
              },
              "ads": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "external_id": {
                      "type": "string",
                      "description": "The ad’s Meta id."
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "ad_set_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "campaign_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "external_id",
                    "name",
                    "ad_set_name",
                    "campaign_name"
                  ]
                },
                "description": "`url_tags_fix` (and its read-only `nav` fallback): the ads whose «Parámetros de URL» do not carry the ad id where attribution reads it, busiest first, at most what one fix accepts (25). Ad set and campaign are named, not identified (the audit carries names only). `campaign_pause`: the campaign’s delivering ads, busiest first (at most 6 — the card’s thumbnails; the live count is the preview’s)."
              }
            },
            "required": [
              "level",
              "external_id",
              "name"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": {},
            "description": "The engine’s own vocabulary, e.g. `{ budget_change_pct: 20 }`. `url_tags_fix`: `{ ads: [{ external_id, expected_url_tags, url_tags }] }` — each ad’s current parameters (null = none) and exactly what it gets. «Enviar a Meta»: `send_setup`/`send_dataset` `{ dataset_id }`, `send_stage` `{ stage, enabled, meta_event, active_ad_sets }`. `campaign_pause`: `{ period_spend, period_days, delivering_ads_count, avg_daily_spend_7d }` — the campaign’s spend in the listed period (the account currency’s major units), the period’s days and its delivering ads, what the card draws at rest (spend → 0, ~spend/days a day); `avg_daily_spend_7d` is the live 7-day daily mean the preview shows (`AdsMoney`: exact decimal + currency), `null` when the list could not know it (real mode reads it only at preview) — fall back to `period_spend / period_days`, labelled as the period. `adset_pause` (D62): `{ ad_ids, period_spend, share, booking_rate, sibling_ad_set, sibling_booking_rate, budget_level }` — the ad set’s delivering ads (bound into the approval: only those the preview read as showing), its share of the campaign’s spend and its booking rate vs its best sibling’s; with a campaign budget Meta moves the money to the sibling. `budget_change` on a campaign (`target.level = \"campaign\"`, `engine_ref.op = \"campaigns.budget\"`, D78) raises or lowers a campaign that owns its budget (Advantage campaign budget) by `budget_change_pct` (±20 % at most, D68): `{ budget_change_pct, budget_level: \"campaign\", budget_type: \"daily\" | \"lifetime\", daily_budget, new_daily_budget (or lifetime_budget, new_lifetime_budget), step_pct }` — amounts in the account currency’s major units, `null` when the status read could not give them; the worker applies the step to the LIVE budget. A raise follows a winner; a lower is the D62 trim one level up (a campaign with more than its even share of the account’s spend booking under half the best other campaign’s rate). Undo = rollback. The optimisation-event cards (the duplicate card `params.optimization_event`, the experiment card, `adset_duplicate_swap`) follow D81: `appointment_booked` until the account reaches ~`first_payment_threshold` (50) first payments a week (`weekly_first_payments`), then `first_payment`. `params.card = \"funnel_leak\"` (D66, D95): the Informe's M2 — the NEW people who wrote from an ad book under 10 % (`/ads/stages` `m2.new_people`, same period) — `{ people, booked, booking_rate, window, benchmark, by_ad: [{ ad_external_id, name, people, booked, rate }], worst: [ad_external_id] }` (`by_ad` = that cohort split by ad, ads with ≥ 10 people; `worst` = at most 3 of them, lowest share first). Agenda capacity (clinics): a `nav` card with `params.card = \"capacity_bottleneck\"` REPLACES an ad's «Pausar» (`variant: \"pause\"`) or a «Subir» (`variant: \"budget\"`) when the specialty the ad books has no usable hour for more than 7 days (or under 2 bookable hours in 14): `{ card, variant, especialidad, mapping_source: \"map\" | \"booked\" | \"name\", unmapped: false, next_slot_day, searched_through, free_minutes_14, booked_minutes_14, fill_14, visit_minutes, evaluation_price, price_above_median, people, booked, booking_rate, median_booking_rate, waitlist_30d }` — counts, minutes and dates, never a probability; `waitlist_30d` = people waiting now for that specialty (the clinic waitlist demand feed), `null` when the feed is absent or 0 — the card then carries no «N personas esperan hora con X hoy» sentence. A `pause_ad` whose ad books an unknown specialty carries `params.capacity = { unmapped: true }` (ask which specialty; `tenant.settings.ads.specialty_map`)."
          },
          "reversible": {
            "type": "boolean"
          },
          "risk": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "reversible",
              "learning_reset",
              "consequences"
            ]
          },
          "engine_ref": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "op": {
                "type": "string",
                "enum": [
                  "recommendations.apply",
                  "ads.pause",
                  "ads.pause_set",
                  "ads.resume",
                  "ads.resume_set",
                  "adsets.budget",
                  "campaigns.budget",
                  "campaigns.pause",
                  "campaigns.resume",
                  "wiring.ad_sets",
                  "quality.utm_refresh",
                  "wiring.recheck",
                  "actions.rollback",
                  "quality.url_tags_apply",
                  "quality.url_tags_undo",
                  "send.setup",
                  "send.dataset",
                  "send.stage",
                  "send.stop",
                  "send.restore"
                ]
              },
              "recommendation_id": {
                "type": "string"
              }
            },
            "required": [
              "op"
            ],
            "description": "Which operation executes it; `null` for `nav`."
          },
          "href": {
            "type": "string",
            "description": "`nav` only: a workspace-relative path (`/anuncios/atribuidos?c=…`, `/anuncios/activar?paso=tag`), or an absolute Meta Ads Manager URL built from Meta ids (`https://www.facebook.com/adsmanager/…`) — open it in a new tab; it changes nothing by itself."
          },
          "cta": {
            "type": "string"
          },
          "facts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fact keys the card is built from."
          },
          "confidence": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string",
                "enum": [
                  "seguro",
                  "probable",
                  "falta_data"
                ]
              },
              "people_needed": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "level",
              "people_needed"
            ],
            "description": "How sure the per-ad verdict behind a pause / scale card is (D64): «seguro», «probable», or «falta data» with ≈ `people_needed` more people. Absent on cards that rest on no per-ad verdict."
          },
          "dismissed_until": {
            "type": [
              "string",
              "null"
            ]
          },
          "disabled_reason": {
            "type": "string",
            "enum": [
              "consent_pending"
            ],
            "description": "Set on a listed action that cannot run yet — show it disabled. `consent_pending`: its consent text is not published yet (`send_protected_move`, the move of the ad sets onto the protected dataset of a restricted clinic dataset)."
          }
        },
        "required": [
          "key",
          "kind",
          "screen",
          "primary",
          "title",
          "why",
          "effect",
          "target",
          "params",
          "reversible",
          "risk",
          "engine_ref",
          "facts",
          "dismissed_until"
        ]
      },
      "AdsMoney": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "amount": {
            "type": "string",
            "description": "Exact decimal string in the currency’s MAJOR units (never a float): `\"12345.5\"`. Parse before arithmetic."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 4217, e.g. `CLP`."
          },
          "display": {
            "type": [
              "string",
              "null"
            ],
            "description": "Formatted for display (`$12.346`); `null` when the currency is unknown."
          }
        },
        "required": [
          "amount",
          "currency",
          "display"
        ],
        "description": "Mean daily spend over the last `spend_window_days` complete days — the «~$X al día» the pause stops. `null` = nothing spent / unknown."
      },
      "AdsCampaignPauseSnapshot": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string",
            "description": "The campaign’s Meta id."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Configured status: ACTIVE | PAUSED | ARCHIVED | DELETED."
          },
          "effective_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta’s delivery verdict (ACTIVE, IN_PROCESS, WITH_ISSUES, …)."
          },
          "status_source": {
            "type": "string",
            "enum": [
              "live",
              "synced"
            ],
            "description": "`live` — read from Meta just now. A preview answers only on a live read: when Meta cannot be read it is 503 `ADS_ACTION_READ_UNAVAILABLE` (retry in a moment)."
          },
          "delivering_ads_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Ads the pause stops right now — «detiene {n} anuncios». `null` = unknown."
          },
          "avg_daily_spend": {
            "$ref": "#/components/schemas/AdsMoney"
          },
          "spend_window_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "budget_level": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "campaign",
              "ad_set"
            ],
            "description": "`campaign` — one campaign budget; `ad_set` — each ad set has its own."
          },
          "daily_budget": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsMoney"
              },
              {
                "description": "The campaign’s own daily budget (budget_level `campaign`)."
              }
            ]
          },
          "lifetime_budget": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsMoney"
              },
              {
                "description": "The campaign’s own lifetime budget, when it runs on one."
              }
            ]
          },
          "as_of": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the status was read (ISO)."
          }
        },
        "required": [
          "external_id",
          "name",
          "status",
          "effective_status",
          "status_source",
          "delivering_ads_count",
          "avg_daily_spend",
          "spend_window_days",
          "budget_level",
          "daily_budget",
          "lifetime_budget",
          "as_of"
        ],
        "description": "`campaign_pause` only: the live campaign, from ONE status read — everything the sheet and the card’s delta draw, no second call."
      },
      "AdsActionPreview": {
        "type": "object",
        "properties": {
          "preview_id": {
            "type": "string",
            "format": "uuid"
          },
          "expires_at": {
            "type": "string"
          },
          "action_key": {
            "type": "string"
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "before": {
                  "type": "string"
                },
                "after": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "kept"
                  ],
                  "description": "`kept`: a fact the action does not change — render ONE value (`before`; `after` repeats it) with `note`. Absent: a before → after change."
                },
                "note": {
                  "type": "string",
                  "description": "`kept` rows: why it stays («Se conserva …»)."
                }
              },
              "required": [
                "key",
                "label",
                "before",
                "after"
              ]
            }
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "consent": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The consent-bearing previews only (duplicate-and-swap, `url_tags_fix`, «Enviar a Meta» when it asks): `consent_terms.text` split into lines, for display (verbatim)."
          },
          "consent_terms": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "version": {
                "type": "string"
              },
              "locale": {
                "type": "string",
                "enum": [
                  "es"
                ]
              },
              "text": {
                "type": "string",
                "description": "The consent text, VERBATIM — render it as is (plain text, `\\n` line breaks, `- ` bullets)."
              },
              "text_hash": {
                "type": "string",
                "description": "SHA-256 (lowercase hex, UTF-8) of `text`."
              }
            },
            "required": [
              "version",
              "locale",
              "text",
              "text_hash"
            ],
            "description": "The consent-bearing previews only: what the owner accepts before confirming. Execute must send `consent: { version, text_hash }`. For `campaign_pause`: every ad stops at once; «Reanudar» brings it back with the same budget, ads paused on their own stay paused, and a pause of 7 days or more can restart Meta’s learning phase. For `url_tags_fix` the text is one sentence: rewriting an ad’s parameters replaces its creative, which can restart the learning phase of the affected ad sets. «Enviar a Meta»: `send_setup` always carries the owner’s authorisation of the send; `send_dataset`, and a `send_stage` off or `send_stop` that stops an event active ad sets optimise on, carry the learning-reset line."
          },
          "reversible": {
            "type": "boolean"
          },
          "engine_op": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "recommendations.apply",
              "ads.pause",
              "ads.pause_set",
              "ads.resume",
              "ads.resume_set",
              "adsets.budget",
              "campaigns.budget",
              "campaigns.pause",
              "campaigns.resume",
              "wiring.ad_sets",
              "quality.utm_refresh",
              "wiring.recheck",
              "actions.rollback",
              "quality.url_tags_apply",
              "quality.url_tags_undo",
              "send.setup",
              "send.dataset",
              "send.stage",
              "send.stop",
              "send.restore"
            ]
          },
          "summary": {
            "type": "string",
            "description": "`campaign_pause` only: the line the sheet leads with — «Pausar «{campaign}» · detiene {n} anuncios · ~$X al día» (the count and the daily figure are dropped when unknown)."
          },
          "campaign": {
            "$ref": "#/components/schemas/AdsCampaignPauseSnapshot"
          }
        },
        "required": [
          "preview_id",
          "expires_at",
          "action_key",
          "rows",
          "warnings",
          "consent",
          "consent_terms",
          "reversible",
          "engine_op"
        ]
      },
      "AdsActionExecution": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "action_key": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "pause_ad",
              "campaign_pause",
              "adset_pause",
              "ad_resume",
              "budget_change",
              "adset_duplicate_swap",
              "utm_refresh",
              "wiring_recheck",
              "url_tags_fix",
              "send_setup",
              "send_stage",
              "send_dataset",
              "send_stop"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "running",
              "succeeded",
              "failed",
              "refused",
              "rolled_back"
            ]
          },
          "target": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string",
                "enum": [
                  "ad",
                  "ad_set",
                  "campaign",
                  "profile"
                ]
              },
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The Meta id of what was touched; `null` for a profile-level action."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "campaign_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The campaign the card named: set on an ad-set budget change (the card is titled with the campaign, the ad set is `name`) and on an ad pause whose campaign was known; `null` otherwise and on rows recorded before this field existed."
              },
              "ads": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "external_id": {
                      "type": "string",
                      "description": "The ad’s Meta id."
                    },
                    "name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "external_id",
                    "name"
                  ]
                },
                "description": "`url_tags_fix` only: the ads the fix was asked to correct (the journal writes «corregiste los enlaces de {n} anuncios»). Each ad’s result is on `outcome.ads[]`."
              }
            },
            "required": [
              "level",
              "external_id",
              "name",
              "campaign_name"
            ]
          },
          "outcome": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "summary": {
                "type": "string"
              },
              "engine_action_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "applied_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "summary",
              "engine_action_id",
              "applied_at"
            ],
            "description": "What happened. `url_tags_fix` adds `partial` (true when fewer ads were corrected than asked — never show «Hecho» then), `corrected_count`, `requested_count` and `ads[]`: per ad `{external_id, name, result: corrected|skipped|failed, code, message, url_tags_after, engine_action_id, undo_available}` (an ad that already carried the parameters counts as corrected; it is undoable when this fix wrote it). Its undo (a rollback row) reports `undone_count`, `drifted_count` (changed in Meta since — never touched), `gone_count` (already undone elsewhere — no longer undoable from here), `reconnect_count` (Meta asks to reconnect), `retryable_count` (no answer or reconnect — the original stays undoable and the next rollback sends only those ads), `requested_count`, `partial` and `ads[]` `{external_id, name, result: undone|drifted|gone|retryable|reconnect, code, message}`; a failed or refused fix still carries `ads[]`. `campaign_pause` (and its «Reanudar», a rollback row) adds `engine_outcome` (`applied` | `no_change` — already in that state, nothing written | `replayed`), `status`, `previous_status`, `campaign_name`, `affected_ads_count` (ads stopped, or freed by the resume; `null` when Meta gave no count) and `journal_line` («pausaste la campaña «X» · 4 anuncios» / «reanudaste …»; `null` on `no_change`). A «Reanudar» never brings back ads that were paused on their own."
          },
          "error": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "reason": {
                "type": "string",
                "enum": [
                  "promoted_object_frozen",
                  "reconnect_required",
                  "permission_gap",
                  "rate_limited",
                  "not_reversible",
                  "forbidden",
                  "consent_required",
                  "engine_error"
                ]
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "code",
              "reason",
              "message"
            ]
          },
          "rollback_available": {
            "type": "boolean"
          },
          "rollback_of": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "simulated": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "action_key",
          "kind",
          "status",
          "target",
          "outcome",
          "error",
          "rollback_available",
          "rollback_of",
          "simulated",
          "created_at",
          "finished_at"
        ]
      },
      "AdsActionExecutionListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AdsActionExecution"
          },
          {
            "type": "object",
            "properties": {
              "created_by": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Display name of the member who confirmed it — only for a caller holding `memberships:read` (sample mode included, for the rows the owner simulated); `null` otherwise, for an API key, and on the two sample example rows. Write «Vitrina» for `null`."
              }
            },
            "required": [
              "created_by"
            ]
          }
        ]
      },
      "AdsTrackingKey": {
        "type": "object",
        "properties": {
          "key_id": {
            "type": "string"
          },
          "public_key": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          },
          "combined_snippet": {
            "type": [
              "string",
              "null"
            ]
          },
          "site_key": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta_pixel_id": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key_id",
          "public_key",
          "snippet",
          "combined_snippet",
          "site_key",
          "meta_pixel_id"
        ]
      },
      "AdsGtmInstaller": {
        "type": "object",
        "properties": {
          "key_id": {
            "type": "string"
          },
          "public_key": {
            "type": "string"
          },
          "site_key": {
            "type": "string"
          },
          "tag_name": {
            "type": "string"
          },
          "trigger": {
            "type": "string"
          },
          "snippet": {
            "type": "string"
          },
          "setup_checklist": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "key_id",
          "public_key",
          "site_key",
          "tag_name",
          "trigger",
          "snippet",
          "setup_checklist"
        ]
      },
      "AdsTrackingStatus": {
        "type": "object",
        "properties": {
          "installed": {
            "type": "boolean",
            "description": "A timestamped event in the last 30 days. It STAYS `true` for 30 days after the last event (a removed tag, or a site without visits, keeps reading `true` for a month): read `reporting` to know whether the tag is sending events now."
          },
          "reporting": {
            "type": "boolean",
            "description": "The tag sent events in the last 24 hours: `events_24h > 0`; when that count could not be read, a `last_seen_at` inside the last 24 hours. `false` with `source: health` means unknown (the daily scan says nothing about the last day), not silent."
          },
          "last_seen_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "events_24h": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "tracker",
              "health"
            ]
          },
          "collector": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "same_origin",
              "cname",
              "none"
            ],
            "description": "Where the tag sends its events: `same_origin` — a collector on the site’s own domain (the only setup Safari does not cap; visitors stay recognised across weeks); `cname` — a verified tracking subdomain (Safari still caps it); `none` — the default endpoint. `null` when it could not be read."
          },
          "booking_widget": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "installed": {
                "type": "boolean",
                "description": "A page loaded the clinic booking widget with this workspace’s `pk_` key in the last 30 days (the tag’s own window)."
              },
              "reporting": {
                "type": "boolean",
                "description": "Seen in the last 24 hours."
              },
              "last_seen_at": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Newest sighting (ISO 8601); `null` = never seen."
              },
              "origins": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "origin": {
                      "type": "string",
                      "description": "The clinic page’s origin, e.g. `https://clinica.cl`."
                    },
                    "last_seen_at": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "origin",
                    "last_seen_at"
                  ]
                },
                "description": "Pages seen running the widget, newest first (at most 10)."
              }
            },
            "required": [
              "installed",
              "reporting",
              "last_seen_at",
              "origins"
            ],
            "description": "The clinic booking widget’s install check (#3709), next to the tag’s. A sighting is recorded each time a page loads the widget with the clinic’s `pk_` key (`GET /widget/clinic/landing`, from an origin the key allows), backed by the widget booking drafts’ page origin. `null` when the workspace has no clinic widget to install (not a clinic, or no active online-booking landing) and in sample mode."
          }
        },
        "required": [
          "installed",
          "reporting",
          "last_seen_at",
          "events_24h",
          "domain",
          "source",
          "collector"
        ],
        "description": "No per-day series: tag events are counted over the last 24 hours only (`events_24h`); a day-by-day count is not available."
      },
      "AdsGtmPendingChange": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "`tag` | `trigger` | `variable` | `folder` | …"
          },
          "name": {
            "type": "string"
          },
          "change": {
            "type": "string",
            "description": "Tag Manager's change status: `added` | `updated` | `deleted` | `conflict` …"
          },
          "ref": {
            "type": "string",
            "description": "The entity’s Tag Manager fingerprint, or the version id — part of the confirmation fingerprint."
          }
        },
        "required": [
          "type",
          "name",
          "change"
        ],
        "example": {
          "type": "tag",
          "name": "Example tag",
          "change": "updated"
        }
      },
      "AdsGtmReview": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "pending_changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsGtmPendingChange"
            },
            "description": "Changes in the «Vitrina Ads» workspace that are NOT the Vitrina tag or trigger. They would be published with it: `publish` refuses with `409 GTM_PENDING_CHANGES` until confirmed."
          },
          "other_workspaces": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "pending_changes": {
                  "type": "integer",
                  "minimum": 0
                }
              },
              "required": [
                "name",
                "pending_changes"
              ]
            },
            "description": "Other workspaces of the container with unpublished changes. Never published by Vitrina — context only."
          },
          "duplicate_loader_tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names of other Custom HTML tags that already load Vitrina’s `tag.js` (a manual install under another name). Remove them in Tag Manager so the tag does not load twice."
          }
        },
        "required": [
          "pending_changes",
          "other_workspaces",
          "duplicate_loader_tags"
        ],
        "description": "Filled by `install` (a live workspace read); `null` elsewhere."
      },
      "AdsGtmState": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean",
            "description": "`false` when this deployment has no Google OAuth client configured — only the manual install (`GET /ads/tracking/installers/gtm`) is available."
          },
          "connection": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "not_connected",
                  "connected",
                  "needs_reconnect"
                ]
              },
              "account_email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "can_edit": {
                "type": "boolean",
                "description": "Granted: list containers, write the tag, preview."
              },
              "can_publish": {
                "type": "boolean",
                "description": "Granted: create and publish a container version (the second consent step, `access: publish`)."
              }
            },
            "required": [
              "status",
              "account_email",
              "can_edit",
              "can_publish"
            ]
          },
          "container": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "path": {
                "type": "string",
                "description": "Tag Manager's own resource path (`accounts/{accountId}/containers/{containerId}`)."
              },
              "code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The container code shown in Tag Manager (`GTM-…`)."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "domains": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            },
            "required": [
              "path",
              "code",
              "name",
              "domains"
            ]
          },
          "install": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "none",
                  "draft",
                  "published",
                  "removed"
                ],
                "description": "`draft` — the tag is written in the «Vitrina Ads» workspace but not published; `published` — live in the container; `removed` — uninstalled in a published version."
              },
              "workspace_path": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The «Vitrina Ads» workspace's Tag Manager path."
              },
              "workspace_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The workspace in the Tag Manager web UI."
              },
              "tag_path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "trigger_path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "site_key": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The site key (`pk_…`) embedded in the installed snippet."
              },
              "preview_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Tag Assistant link for the last quick preview."
              },
              "previewed_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "published_version_path": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "published_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "state",
              "workspace_path",
              "workspace_url",
              "tag_path",
              "trigger_path",
              "site_key",
              "preview_url",
              "previewed_at",
              "published_version_path",
              "published_at"
            ]
          },
          "review": {
            "$ref": "#/components/schemas/AdsGtmReview"
          }
        },
        "required": [
          "available",
          "connection",
          "container",
          "install",
          "review"
        ],
        "example": {
          "available": true,
          "connection": {
            "status": "connected",
            "account_email": "admin@example.com",
            "can_edit": true,
            "can_publish": false
          },
          "container": {
            "path": "accounts/1234567890/containers/987654321",
            "code": "GTM-XXXXXXX",
            "name": "example.com",
            "domains": [
              "example.com"
            ]
          },
          "install": {
            "state": "draft",
            "workspace_path": "accounts/1234567890/containers/987654321/workspaces/7",
            "workspace_url": "https://tagmanager.google.com/#/container/accounts/1234567890/containers/987654321/workspaces/7",
            "tag_path": "accounts/1234567890/containers/987654321/workspaces/7/tags/12",
            "trigger_path": "accounts/1234567890/containers/987654321/workspaces/7/triggers/11",
            "site_key": "<site-key>",
            "preview_url": null,
            "previewed_at": null,
            "published_version_path": null,
            "published_at": null
          },
          "review": null
        }
      },
      "AdsGtmConnect": {
        "type": "object",
        "properties": {
          "authorize_url": {
            "type": "string"
          }
        },
        "required": [
          "authorize_url"
        ],
        "example": {
          "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=<client-id>"
        }
      },
      "AdsGtmConnectBody": {
        "type": "object",
        "properties": {
          "return_to": {
            "type": "string",
            "format": "uri",
            "description": "Absolute URL on the app's own origin where the browser lands after the consent (`?gtm=connected`, or `?gtm_error=<code>`)."
          },
          "access": {
            "type": "string",
            "enum": [
              "edit",
              "publish"
            ],
            "default": "edit",
            "description": "`edit` — the minimal consent (read containers, write the tag, preview). `publish` — adds creating and publishing container versions, asked incrementally the first time the admin publishes."
          }
        },
        "required": [
          "return_to"
        ],
        "example": {
          "return_to": "https://app.example.com/w/example/ads",
          "access": "edit"
        }
      },
      "AdsGtmConnectCompleteBody": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "minLength": 1,
            "description": "`gtm_code` from the return URL."
          },
          "state": {
            "type": "string",
            "minLength": 1,
            "description": "`gtm_state` from the return URL."
          }
        },
        "required": [
          "code",
          "state"
        ],
        "example": {
          "code": "<code>",
          "state": "<state>"
        }
      },
      "AdsGtmContainers": {
        "type": "object",
        "properties": {
          "accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "path": {
                  "type": "string",
                  "description": "`accounts/{accountId}`."
                },
                "name": {
                  "type": "string"
                },
                "containers": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "path": {
                        "type": "string",
                        "description": "`accounts/{accountId}/containers/{containerId}`."
                      },
                      "code": {
                        "type": "string",
                        "description": "`GTM-…`."
                      },
                      "name": {
                        "type": "string"
                      },
                      "domains": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "suggested": {
                        "type": "boolean",
                        "description": "The container's domains match the site's."
                      }
                    },
                    "required": [
                      "path",
                      "code",
                      "name",
                      "domains",
                      "suggested"
                    ]
                  },
                  "description": "Web containers only."
                }
              },
              "required": [
                "path",
                "name",
                "containers"
              ]
            }
          },
          "suggested_path": {
            "type": [
              "string",
              "null"
            ],
            "description": "The container to pre-select: the first whose domains match the site's."
          },
          "site_hosts": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The site hosts the suggestion was matched against."
          }
        },
        "required": [
          "accounts",
          "suggested_path",
          "site_hosts"
        ],
        "example": {
          "accounts": [
            {
              "path": "accounts/1234567890",
              "name": "Example",
              "containers": [
                {
                  "path": "accounts/1234567890/containers/987654321",
                  "code": "GTM-XXXXXXX",
                  "name": "example.com",
                  "domains": [
                    "example.com"
                  ],
                  "suggested": true
                }
              ]
            }
          ],
          "suggested_path": "accounts/1234567890/containers/987654321",
          "site_hosts": [
            "example.com"
          ]
        }
      },
      "AdsGtmSelectContainerBody": {
        "type": "object",
        "properties": {
          "container_path": {
            "type": "string",
            "pattern": "^accounts\\/\\d+\\/containers\\/\\d+$",
            "description": "A `path` from `GET /ads/tracking/gtm/containers`."
          }
        },
        "required": [
          "container_path"
        ],
        "example": {
          "container_path": "accounts/1234567890/containers/987654321"
        }
      },
      "AdsGtmPublishBody": {
        "type": "object",
        "properties": {
          "pending_fingerprint": {
            "type": "string",
            "description": "Required with `include_pending_changes: true`: the `details.fingerprint` of the `409 GTM_PENDING_CHANGES` a person confirmed. A list that changed since is refused again."
          },
          "include_pending_changes": {
            "type": "boolean",
            "default": false,
            "description": "Confirm publishing the workspace changes that are not Vitrina’s (listed by the `409 GTM_PENDING_CHANGES` refusal). Never set it without having shown that list to a person."
          }
        },
        "example": {
          "include_pending_changes": false
        }
      },
      "AdsFeedOrigin": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "platform": {
            "type": "string",
            "enum": [
              "whatsapp",
              "instagram",
              "messenger",
              "form",
              "google"
            ],
            "description": "Where the person came from the ad: a message on `whatsapp` / `instagram` / `messenger`, a Meta instant `form`, or a `google` Search ad."
          },
          "external_id": {
            "type": "string",
            "description": "The AD’s platform id (never a post id)."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "What to render: the ad’s name in the ad account, else its headline, else `null` («Mensaje por un anuncio de Instagram»)."
          },
          "ad_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad’s name in the ad account; `null` when unknown."
          },
          "headline": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad’s headline — the advertiser’s copy, never the customer’s words."
          },
          "campaign": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The campaign’s platform id, when known."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "external_id",
              "name"
            ],
            "description": "The ad’s campaign, when the ads read knows it."
          },
          "thumbnail_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Still image of the creative: Vitrina’s durable copy when `media_archived`, else the platform’s own link, which expires (render with a fallback)."
          },
          "media_archived": {
            "type": "boolean"
          },
          "first_message_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the person first wrote from the ad (ISO 8601)."
          },
          "matches_credit": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "`true` when the credited campaign (`ad.campaign`) is this ad’s campaign, `false` when the credit went to another campaign, `null` when unknown or not credited yet."
          },
          "imported_from": {
            "type": "string",
            "enum": [
              "vambe"
            ],
            "description": "Set when the origin is a HISTORICAL ad click another platform observed before Vitrina (a historical contact import): that platform. There is no Vitrina conversation behind it, and `first_message_at` is the click’s instant there. Absent otherwise."
          }
        },
        "required": [
          "platform",
          "external_id",
          "name",
          "ad_name",
          "headline",
          "campaign",
          "thumbnail_url",
          "media_archived",
          "first_message_at",
          "matches_credit"
        ],
        "description": "The ad that opened the originating conversation — Vitrina’s own fact from the first message (a click-to-WhatsApp or Instagram / Messenger ad referral), known at once. Not a credit claim: `ad` is the credit. `null` when no ad opened the thread (or it was a boosted post)."
      },
      "AdsFeedItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "stage": {
            "type": "string",
            "enum": [
              "lead_created",
              "web_form",
              "lead_qualified",
              "appointment_booked",
              "checkout_started",
              "deposit_paid",
              "appointment_attended",
              "quote_presented",
              "closed_won",
              "first_payment",
              "returning_first_payment",
              "payment_received"
            ],
            "description": "The outcome stage. `first_payment` = a clinic’s first payment of an accepted plan («Primer pago»), the Purchase Meta receives after the switch. `web_form` = a landing-page form a connected CRM reported («Formulario web»); `checkout_started` = the person opened a Vitrina payment link («Abrió el link de pago»); `deposit_paid` = a deposit (booking, evaluation or reservation) fully paid («Abono pagado»)."
          },
          "source": {
            "type": "string",
            "enum": [
              "vitrina",
              "gohighlevel",
              "mercadopago"
            ],
            "description": "Which system recorded the outcome: `vitrina` — Vitrina itself (a conversation, a booking, a payment it registered); `gohighlevel` / `mercadopago` — the provider the workspace connected reported it to the attribution engine (a Website ad’s form, booking or payment), and Vitrina lists it. Such a row may have no contact (`contact.id` `\"\"`): a healthcare workspace’s provider events never carry the person."
          },
          "occurred_at": {
            "type": "string",
            "description": "When the outcome happened."
          },
          "created_at": {
            "type": "string",
            "description": "When Vitrina recorded it — the cursor field, microsecond-exact."
          },
          "matched": {
            "type": "boolean",
            "description": "The engine accepted the outcome as a conversion it can attribute. Whether the credit can still arrive is `attribution`."
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "CLP the outcome carried (a payment, a quote, a checkout’s deposit asked for); `null` when it carried none (never 0)."
          },
          "action_source": {
            "type": "string",
            "enum": [
              "website",
              "business_messaging",
              "physical_store",
              "system_generated",
              "phone_call",
              "chat",
              "email",
              "app",
              "other"
            ]
          },
          "contact": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "description": "The CONTACT id (`/contacts/{id}`) — never a roster patient id; the ficha is `patient.id`."
              },
              "display_name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The contact’s name, only when the caller holds `contacts:read`; when the contact has none, the linked roster patient’s name (only under the `patient` identity gate). Else `null`."
              },
              "key": {
                "type": "string",
                "description": "Last 4 hex of the contact id — or, for a provider-recorded row with no contact, of the provider’s opaque person key: the same person keeps the same key across their rows."
              }
            },
            "required": [
              "id",
              "display_name",
              "key"
            ]
          },
          "ad": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "ad_set_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "creative_thumbnail_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The ad’s thumbnail (Ads v3). `null` on real data today (the engine’s credit is campaign-grain — it names no ad) and in sample mode (the UI draws a placeholder); render the megaphone glyph for `null`."
              },
              "campaign": {
                "type": "object",
                "properties": {
                  "external_id": {
                    "type": "string",
                    "description": "The campaign’s PLATFORM id (what `campaign_id` filters on)."
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "external_id",
                  "name"
                ]
              }
            },
            "required": [
              "name",
              "ad_set_name",
              "creative_thumbnail_url",
              "campaign"
            ],
            "description": "The campaign the engine credited; `null` until it has. Campaign-grain today: `name`/`ad_set_name` are `null` for real data."
          },
          "credited_weight": {
            "type": [
              "number",
              "null"
            ]
          },
          "touch_count": {
            "type": [
              "integer",
              "null"
            ]
          },
          "identity": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "source": {
                "type": "string",
                "enum": [
                  "auto_link"
                ]
              },
              "method": {
                "type": "string",
                "enum": [
                  "phone_unique",
                  "email_unique",
                  "chat_rut"
                ]
              }
            },
            "required": [
              "source",
              "method"
            ],
            "description": "Set when the contact was matched to the patient AUTOMATICALLY (an identifier held by exactly one patient and one contact, or — `chat_rut` — the patient's RUT the contact typed in its own ad chat, corroborated) — show «Vinculado automáticamente» with the method. `null`/absent = a family bond, a booking link or the contact itself. Undo with `POST /clinic/auto-links/{id}/reject`."
          },
          "origin": {
            "$ref": "#/components/schemas/AdsFeedOrigin"
          },
          "origin_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "engine",
              "conversation",
              "history"
            ],
            "description": "Which source the row names its ad from: `engine` — `ad` (the credited campaign) is set; `history` — `ad` is `null` and `origin` is a HISTORICAL click another platform observed (a historical contact import; no conversation behind it) — say where it came from, never «mensaje por el anuncio»; `conversation` — `ad` is `null` and `origin` is set: show the origin, never «anuncio por confirmar» (whether the credit may still come is `attribution`); `null` — neither is known."
          },
          "attribution": {
            "type": "string",
            "enum": [
              "credited",
              "pending",
              "final"
            ],
            "description": "Whether the credit (`ad`) can still arrive: `credited` — `ad` is set; `pending` — not yet («atribución por confirmar» beside `origin`, else «anuncio por confirmar»); `final` — it never will: the contact was removed from attribution, the outcome is not one an ad can be credited with, or 72 hours after the outcome’s time a complete credit read still names no campaign. Show `origin` alone, or «sin anuncio» — never «por confirmar». A row is never settled while the credit read is incomplete (`meta.enrichment` not `ready`)."
          },
          "attribution_reason": {
            "type": "string",
            "enum": [
              "credited",
              "awaiting_credit",
              "kept_local",
              "erased",
              "not_attributable",
              "email",
              "no_ad_found"
            ],
            "description": "WHY `attribution` is what it is — the honest label: `credited` — `ad` is set; `awaiting_credit` — sent, the credit may still arrive (`pending`); `kept_local` — recorded in Vitrina only and never sent (a minor’s or a guardian-reached patient’s fact): no ad can ever be credited; `erased` — the contact was removed from attribution; `not_attributable` — the engine answered it is not a conversion it attributes; `email` — an email thread, never an ad click; `no_ad_found` — delivered over 72 h ago and a complete credit read names no campaign (organic, or an existing patient’s payment). Every value but `credited` / `awaiting_credit` is `final`."
          },
          "patient_link": {
            "type": "string",
            "enum": [
              "linked",
              "none",
              "unknown"
            ],
            "description": "Whether this is a clinic ROSTER patient: `linked` — `patient` is set; `none` — it resolves to no roster patient (not a patient, or a non-clinic workspace): link to the contact instead; `unknown` — the roster could not be read right now (a lookup failure, NOT «no patient»): retry, never say «no vinculado»."
          },
          "patient": {
            "$ref": "#/components/schemas/AdsLinkedPatient"
          }
        },
        "required": [
          "id",
          "stage",
          "source",
          "occurred_at",
          "created_at",
          "matched",
          "value",
          "action_source",
          "contact",
          "ad",
          "credited_weight",
          "touch_count",
          "origin",
          "origin_source",
          "attribution",
          "attribution_reason",
          "patient_link",
          "patient"
        ]
      },
      "AdsJourneyStepAd": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad’s PLATFORM id; `null` when only the campaign is known."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "ad_set_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "campaign": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "external_id": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The campaign’s PLATFORM id."
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "external_id",
              "name"
            ]
          },
          "creative_thumbnail_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "The ad’s thumbnail when known; render the megaphone glyph for `null`."
          }
        },
        "required": [
          "external_id",
          "name",
          "ad_set_name",
          "campaign",
          "creative_thumbnail_url"
        ]
      },
      "AdsJourneyStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable key for the step. For a step recorded by Vitrina it is the feed item id (`GET /ads/feed` `data[].id`)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "ad",
              "click",
              "visit",
              "chat",
              "form",
              "outcome",
              "booking",
              "checkout",
              "attended",
              "quote",
              "accepted",
              "payment"
            ],
            "description": "Touches: `ad` (an off-site ad touch — an ad click or a click-to-WhatsApp referral), `click` (a visit that landed from an ad click, or a tracked link click), `visit` (a site visit), `chat` (the first message, or a social interaction) — only when there was a conversation: a Website ad’s person has no `chat` step. «Mensaje por el anuncio X»: a `chat` step with `origin: vitrina`, `stage: null`, `id` = the conversation’s id and the ad on `ad`, at the moment the person first wrote from the ad — one per ad-opened conversation; the same ad is never also on the first-message fact, and the engine’s touch of that same referral (the ad’s, or a click-id-only one) is folded into it, credit mark included. Recorded steps, in funnel order: `chat` (first message), `form` (a landing-page form, «Formulario web»), `booking`, `checkout` (a payment link opened, «Abrió el link de pago»), `attended`, `quote`, `accepted` (a presupuesto accepted / a sale closed), `payment` (a payment, or a deposit paid, «Abono pagado»); `outcome` for anything else."
          },
          "origin": {
            "type": "string",
            "enum": [
              "engine",
              "vitrina"
            ],
            "description": "`engine` = a touch the attribution engine observed; `vitrina` = a step Vitrina recorded itself."
          },
          "stage": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "lead_created",
              "web_form",
              "lead_qualified",
              "appointment_booked",
              "checkout_started",
              "deposit_paid",
              "appointment_attended",
              "quote_presented",
              "closed_won",
              "payment_received",
              "first_payment",
              "returning_first_payment"
            ],
            "description": "The outcome stage of a recorded step (the vocabulary key); `null` on a touch. `first_payment` is a plan’s first payment (shown once, in place of that payment’s `payment_received`)."
          },
          "label": {
            "type": "string",
            "description": "Ready to render, in the workspace’s own vocabulary."
          },
          "detail": {
            "type": [
              "string",
              "null"
            ]
          },
          "at": {
            "type": "string",
            "description": "When it happened (ISO 8601)."
          },
          "channel": {
            "type": "string",
            "description": "A channel word: `meta`, `google`, `instagram`, `whatsapp`, `web`, `email`, `phone`, … Never a URL or a domain."
          },
          "credited": {
            "type": "boolean",
            "description": "The touch the outcome was credited to, under `model`."
          },
          "join_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ctwa_referral",
              "click_id",
              "link_token",
              "anonymous_id"
            ],
            "description": "How the touch is tied to this person, when proven: `ctwa_referral` (the click-to-WhatsApp / Instagram ad referral), `click_id` (the ad click id the outcome carried), `link_token` (a link Vitrina sent), `anonymous_id` (the same browser). `null` on recorded steps; `ctwa_referral` on the «Mensaje por el anuncio» step."
          },
          "click_id_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "The KIND of ad click id the touch carried (`fbclid`, `gclid`, `gbraid`, `wbraid`, `ctwa_clid`, …) — never its value."
          },
          "ad": {
            "$ref": "#/components/schemas/AdsJourneyStepAd"
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "CLP on money steps (`quote`, `accepted`, `payment`); `null` otherwise."
          },
          "current": {
            "type": "boolean",
            "description": "This step is the feed row the journey was opened from."
          },
          "imported_from": {
            "type": "string",
            "enum": [
              "vambe"
            ],
            "description": "Set on a HISTORICAL ad touch another platform observed before Vitrina (a historical contact import): that platform. Such a step is `kind: ad`, `origin: vitrina`, never credited by Vitrina; when the engine returns the same click it is folded into the engine’s touch, which then carries this mark. Absent otherwise."
          }
        },
        "required": [
          "id",
          "kind",
          "origin",
          "stage",
          "label",
          "detail",
          "at",
          "credited",
          "join_method",
          "click_id_kind",
          "ad",
          "value",
          "current"
        ]
      },
      "AdsJourneyPerson": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "conversion_count": {
            "type": "integer",
            "description": "Every conversion of the person, whole history — each stage counts (a lead, a booking and a payment are three)."
          },
          "attended_count": {
            "type": "integer",
            "description": "«Veces que vino»: the attended entry citas recorded for this contact («Cita atendida» — distinct `appointment_attended` steps). Clinics only for now: other workspaces record no attended stage, so it is `0` there. Never inferred from bookings. Show a «vino n veces» badge from 2 on."
          },
          "touch_count": {
            "type": "integer",
            "description": "Marketing touches, whole history."
          },
          "first_touch_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_touch_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "conversion_count",
          "attended_count",
          "touch_count",
          "first_touch_at",
          "last_touch_at"
        ],
        "description": "Set exactly when `scope` is `person`."
      },
      "AdsJourney": {
        "type": "object",
        "properties": {
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AdsJourneyStep"
            },
            "description": "Oldest first; at the same instant a touch precedes the step it led to, then by `id` — the same order on every read."
          },
          "touch_count": {
            "type": "integer"
          },
          "credited_weight": {
            "type": [
              "number",
              "null"
            ]
          },
          "model": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "conversion",
              "person"
            ],
            "description": "The scope the journey was read at (see the `scope` parameter)."
          },
          "source": {
            "type": "string",
            "enum": [
              "engine",
              "mixed",
              "vitrina"
            ],
            "description": "`mixed` = the attribution engine’s touches interleaved with the steps Vitrina recorded; `engine` = touches only; `vitrina` = only the steps Vitrina recorded (see `reason`)."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "role",
              "unmatched",
              "pending",
              "unavailable",
              "scope"
            ],
            "description": "Why the ad path is absent or incomplete — set whenever `source` is `vitrina`, and `unavailable` also when the touches could only be read in part within the time budget: `unmatched` — no touch leads to this outcome / person (arrived without an ad or a visit that can be traced); `pending` — not readable yet (retry later); `unavailable` — the attribution engine could not be read now; `scope` — the workspace’s attribution access refused the read. `role` is retired and never sent."
          },
          "person": {
            "$ref": "#/components/schemas/AdsJourneyPerson"
          },
          "truncated": {
            "type": "boolean",
            "description": "`true` when the OLDEST touches of a long history were left out: `steps` is the newest window (the credited touch and the latest steps are always in it). Not a degraded state — `person` carries the whole-history totals. Show «mostrando lo más reciente»."
          },
          "patient_link": {
            "type": "string",
            "enum": [
              "linked",
              "none",
              "unknown"
            ],
            "description": "The opened row’s `patient_link` — identical to the feed item’s."
          },
          "patient": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdsLinkedPatient"
              },
              {
                "description": "The opened row’s linked roster patient — identical to the feed item’s `patient` (same identity gate)."
              }
            ]
          }
        },
        "required": [
          "steps",
          "touch_count",
          "credited_weight",
          "model",
          "scope",
          "source",
          "reason",
          "person",
          "truncated",
          "patient_link",
          "patient"
        ]
      },
      "AdsConversionSource": {
        "type": "object",
        "properties": {
          "crm": {
            "$ref": "#/components/schemas/AdsConversionSourceCrm"
          },
          "payments": {
            "$ref": "#/components/schemas/AdsConversionSourcePayments"
          }
        },
        "required": [
          "crm",
          "payments"
        ],
        "description": "The workspace’s Conversion source, derived from its live connections (never stored): a connected CRM is the source of bookings and sales while it is connected, Vitrina otherwise; payments come from the connected payment provider, Vitrina otherwise. Disconnecting the CRM switches `crm` to `vitrina` at once."
      },
      "AdsExperimentOffer": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "not_sending",
              "dataset_restricted",
              "dataset_unknown",
              "already_open"
            ],
            "description": "Why it is not offered: nothing reaches Meta yet, the dataset is restricted, the dataset check has not run, or an experiment is already open."
          },
          "suggested_test_event": {
            "type": "string",
            "enum": [
              "appointment_booked",
              "first_payment"
            ],
            "description": "D81: the event the test arm should optimise for — `appointment_booked` («cita agendada») until the account reaches ~`first_payment_threshold` new patients’ first payments a week, then `first_payment`. The default `test_event` of `POST /ads/experiments`. Absent when the count could not be read."
          },
          "weekly_first_payments": {
            "type": [
              "number",
              "null"
            ],
            "description": "New patients’ first payments a week (one decimal; «Envío a Meta»’s «Por semana»), `null` = unknown."
          },
          "first_payment_threshold": {
            "type": "integer",
            "description": "First payments a week from which Meta optimises on the first payment (~50)."
          }
        },
        "required": [
          "available",
          "reason"
        ]
      },
      "AdsExperiment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "running",
              "ended"
            ]
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ended_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "readout_from": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "The comparison’s first clinic day when chosen by the owner; `null` = the day the last arm leaves learning."
          },
          "read_early": {
            "type": "boolean",
            "description": "The owner chose to read before every arm left learning."
          },
          "created_at": {
            "type": "string"
          },
          "arms": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "arm_key": {
                  "type": "string"
                },
                "role": {
                  "type": "string",
                  "enum": [
                    "control",
                    "test"
                  ]
                },
                "optimization_event": {
                  "type": "string",
                  "enum": [
                    "conversations",
                    "first_payment",
                    "closed_won",
                    "appointment_attended",
                    "appointment_booked",
                    "lead_created"
                  ],
                  "description": "What the arm’s ad sets optimise for: `conversations` (Meta’s own «conversaciones», the control) or a stage Vitrina sends to Meta (`first_payment` = «primer pago», `closed_won` = «presupuesto aceptado», `appointment_attended` = «cita atendida»)."
                },
                "meta_campaign": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "meta_ad_sets": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "budget_share": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "The planned share of the daily budget, 0–100."
                },
                "learning_status": {
                  "type": "string",
                  "enum": [
                    "learning",
                    "learning_limited",
                    "active",
                    "unknown"
                  ],
                  "description": "Meta’s learning stage for the arm’s ad sets, READ from Meta (never guessed): `learning` (Aprendiendo), `learning_limited` (Aprendizaje limitado — too few events to stabilise), `active` (out of learning), `unknown` until a read answers. The least advanced ad set decides."
                },
                "learning_checked_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "left_learning_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The first read that found every ad set of the arm out of learning."
                }
              },
              "required": [
                "arm_key",
                "role",
                "optimization_event",
                "meta_campaign",
                "meta_ad_sets",
                "budget_share",
                "learning_status",
                "learning_checked_at",
                "left_learning_at"
              ]
            }
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "started_at",
          "ended_at",
          "readout_from",
          "read_early",
          "created_at",
          "arms"
        ]
      },
      "VendeTuAutoLandingDestination": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "enabled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "enabled_source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "lanzamiento",
              "manual"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where the landing lane's ads send people: https://<Sitio host>/vende-tu-auto, else external_url. null = nowhere yet."
          },
          "kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "sitio",
              "external"
            ]
          },
          "external_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "embed_snippet": {
            "type": [
              "string",
              "null"
            ],
            "description": "HTML to paste on the dealer's own page (external_url). Its key is origin-locked to the registered storefront domains."
          }
        },
        "required": [
          "enabled",
          "enabled_at",
          "enabled_source",
          "url",
          "kind",
          "external_url",
          "embed_snippet"
        ]
      },
      "AdsAudienceStatus": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "coming_soon",
              "unavailable",
              "off",
              "preparing",
              "live",
              "error",
              "unknown"
            ],
            "description": "`coming_soon` = the measurement engine cannot build it yet («Pronto»); `unavailable` = this workspace cannot have one (`reason`); `off` = offered, not turned on; `preparing` / `live` / `error` once on; `unknown` = the engine could not be read now."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "names": {
            "type": "object",
            "properties": {
              "audience": {
                "type": "string",
                "description": "The fixed name in Meta: «Vitrina · Clientes que pagaron»."
              },
              "lookalike": {
                "type": "string"
              }
            },
            "required": [
              "audience",
              "lookalike"
            ]
          },
          "size": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "lower": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "upper": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "lower",
              "upper"
            ],
            "description": "The matched-size range Meta reports."
          },
          "last_refreshed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "lookalike": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "state": {
                "type": "string"
              },
              "ratio": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "state",
              "ratio"
            ]
          },
          "optin": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "accepted_at": {
                "type": "string"
              },
              "consent_version": {
                "type": "string"
              },
              "current": {
                "type": "boolean"
              },
              "lookalike": {
                "type": "boolean"
              }
            },
            "required": [
              "accepted_at",
              "consent_version",
              "current",
              "lookalike"
            ]
          },
          "consent": {
            "type": "object",
            "properties": {
              "version": {
                "type": "string"
              },
              "text": {
                "type": "string",
                "description": "The consent the owner reads verbatim (Spanish)."
              },
              "text_hash": {
                "type": "string",
                "description": "SHA-256 of `text`; sent back to turn it on."
              }
            },
            "required": [
              "version",
              "text",
              "text_hash"
            ]
          }
        },
        "required": [
          "state",
          "reason",
          "names",
          "size",
          "last_refreshed_at",
          "lookalike",
          "optin",
          "consent"
        ]
      },
      "InboxViewFilters": {
        "type": "object",
        "properties": {
          "channel": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40
          },
          "messagingAccountId": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "conversation",
              "marketplace",
              "manual",
              "import",
              "ai_agent",
              "chileautos",
              "yapo",
              "mercadolibre",
              "website"
            ]
          },
          "convStatus": {
            "type": "string",
            "enum": [
              "open",
              "pending",
              "snoozed",
              "resolved",
              "closed"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "handler": {
            "type": "string",
            "enum": [
              "bot",
              "human",
              "external"
            ]
          },
          "assigneeUserId": {
            "type": "string",
            "format": "uuid"
          },
          "unassigned": {
            "type": "boolean"
          },
          "teamId": {
            "type": "string",
            "format": "uuid"
          },
          "aiReplied": {
            "type": "boolean"
          },
          "fromAd": {
            "type": "boolean"
          },
          "fromCampaign": {
            "type": "boolean"
          },
          "waiting": {
            "anyOf": [
              {
                "type": "boolean",
                "enum": [
                  true
                ]
              },
              {
                "type": "string",
                "enum": [
                  "closed"
                ]
              }
            ]
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "op": {
                  "type": "string",
                  "enum": [
                    "contains",
                    "equals"
                  ],
                  "default": "contains"
                },
                "value": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                }
              },
              "required": [
                "key",
                "value"
              ],
              "additionalProperties": false
            },
            "minItems": 1,
            "maxItems": 10
          },
          "tagIds": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "minItems": 1,
            "maxItems": 20
          }
        },
        "additionalProperties": false,
        "description": "A subset of `GET /conversations` filters, same names and meaning. `attributes` are ANDed (`{ key, op: contains|equals, value }`, case- and accent-insensitive on the stored value as text; `key: \"*\"` matches the value under ANY attribute key); `tagIds` matches ANY of the tags; `waiting: true` lists only the matches a customer is waiting on (`closed`: the ones left out of the queue). At least one criterion other than `waiting` is required.",
        "example": {
          "attributes": [
            {
              "key": "*",
              "op": "contains",
              "value": "18 pro max"
            }
          ],
          "active": true
        }
      },
      "Property": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "operation": {
            "type": "string",
            "enum": [
              "venta",
              "arriendo",
              "venta_y_arriendo"
            ],
            "description": "venta | arriendo | venta_y_arriendo"
          },
          "property_type": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "oficina",
              "local_comercial",
              "terreno",
              "parcela",
              "bodega",
              "estacionamiento",
              "industrial",
              "otro"
            ],
            "description": "Tipo de propiedad."
          },
          "status": {
            "type": "string",
            "enum": [
              "disponible",
              "bloqueada",
              "reservada",
              "promesada",
              "vendida",
              "arrendada",
              "pausada"
            ],
            "description": "Estado: disponible | bloqueada | reservada | promesada | vendida | arrendada | pausada. `bloqueada` and `promesada` only occur on a proyecto unidad."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "sector": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 160
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Dirección. PRIVATE: never shown to an AI agent; shared with a buyer once a visit is booked."
          },
          "sale_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Sale price, in `sale_price_currency`. Needs operation venta."
          },
          "sale_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "rent_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Monthly rent, in `rent_price_currency`. Needs operation arriendo."
          },
          "rent_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "common_expenses_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Gastos comunes, CLP/mes."
          },
          "property_tax_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Contribuciones, CLP por cuota trimestral."
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros útiles."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros totales."
          },
          "parking_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "storage_units": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999,
            "description": "Bodegas."
          },
          "orientation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "norte",
              "sur",
              "oriente",
              "poniente",
              "nororiente",
              "norponiente",
              "suroriente",
              "surponiente"
            ]
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 300,
            "description": "Piso."
          },
          "year_built": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1800,
            "maximum": 2100
          },
          "furnished": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Amoblado."
          },
          "pets_allowed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Acepta mascotas."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "broker_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The corredor in charge (a member user id)."
          },
          "owner_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Propietario. PRIVATE: never shown to an AI agent."
          },
          "owner_phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "owner_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 320,
            "format": "email"
          },
          "source": {
            "type": "string"
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "protected_fields": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Fields a person edited on a sourced row; no sync overwrites them (ADR 0023)."
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The proyecto when the row is a unidad (Módulo Proyectos); null for a propiedad of the cartera."
          },
          "unit_model_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The unidad’s tipología, when it has one."
          },
          "unit_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "The unidad’s number (unidades only)."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "description": "Metros de terraza (unidades only)."
          },
          "hold_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a bloqueada unidad’s hold ends."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "sale_price": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "UF",
                  "CLP"
                ]
              },
              "amount_clp": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "amount_uf": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "amount",
              "currency",
              "amount_clp",
              "amount_uf"
            ],
            "description": "The price as published, plus both conversions with the day’s UF (null when no UF value is known)."
          },
          "rent_price": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "UF",
                  "CLP"
                ]
              },
              "amount_clp": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "amount_uf": {
                "type": [
                  "number",
                  "null"
                ]
              }
            },
            "required": [
              "amount",
              "currency",
              "amount_clp",
              "amount_uf"
            ],
            "description": "The price as published, plus both conversions with the day’s UF (null when no UF value is known)."
          }
        },
        "required": [
          "id",
          "tenant_id",
          "display_id",
          "title",
          "description",
          "operation",
          "property_type",
          "status",
          "region",
          "comuna",
          "sector",
          "address",
          "sale_price_amount",
          "sale_price_currency",
          "rent_price_amount",
          "rent_price_currency",
          "common_expenses_clp",
          "property_tax_clp",
          "bedrooms",
          "bathrooms",
          "usable_area_m2",
          "total_area_m2",
          "parking_spaces",
          "storage_units",
          "orientation",
          "floor",
          "year_built",
          "furnished",
          "pets_allowed",
          "photos",
          "broker_user_id",
          "owner_name",
          "owner_phone",
          "owner_email",
          "source",
          "external_ref",
          "protected_fields",
          "project_id",
          "unit_model_id",
          "unit_number",
          "terrace_area_m2",
          "hold_expires_at",
          "created_by",
          "created_at",
          "updated_at",
          "sale_price",
          "rent_price"
        ]
      },
      "CreatePropertyBody": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "operation": {
            "type": "string",
            "enum": [
              "venta",
              "arriendo",
              "venta_y_arriendo"
            ],
            "description": "venta | arriendo | venta_y_arriendo"
          },
          "property_type": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "oficina",
              "local_comercial",
              "terreno",
              "parcela",
              "bodega",
              "estacionamiento",
              "industrial",
              "otro"
            ],
            "description": "Tipo de propiedad."
          },
          "status": {
            "type": "string",
            "enum": [
              "disponible",
              "reservada",
              "vendida",
              "arrendada",
              "pausada"
            ],
            "description": "Estado comercial: disponible | reservada | vendida | arrendada | pausada. `pausada` takes it off the market and keeps it in the cartera."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "sector": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 160
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Dirección. PRIVATE: never shown to an AI agent; shared with a buyer once a visit is booked."
          },
          "sale_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Sale price, in `sale_price_currency`. Needs operation venta."
          },
          "sale_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "rent_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Monthly rent, in `rent_price_currency`. Needs operation arriendo."
          },
          "rent_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "common_expenses_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Gastos comunes, CLP/mes."
          },
          "property_tax_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Contribuciones, CLP por cuota trimestral."
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros útiles."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros totales."
          },
          "parking_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "storage_units": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999,
            "description": "Bodegas."
          },
          "orientation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "norte",
              "sur",
              "oriente",
              "poniente",
              "nororiente",
              "norponiente",
              "suroriente",
              "surponiente"
            ]
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 300,
            "description": "Piso."
          },
          "year_built": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1800,
            "maximum": 2100
          },
          "furnished": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Amoblado."
          },
          "pets_allowed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Acepta mascotas."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Photo URLs in display order; the first is the cover."
          },
          "broker_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The corredor in charge (a member user id)."
          },
          "owner_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Propietario. PRIVATE: never shown to an AI agent."
          },
          "owner_phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "owner_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 320,
            "format": "email"
          }
        },
        "required": [
          "title",
          "operation",
          "property_type"
        ]
      },
      "UpdatePropertyBody": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "operation": {
            "type": "string",
            "enum": [
              "venta",
              "arriendo",
              "venta_y_arriendo"
            ],
            "description": "venta | arriendo | venta_y_arriendo"
          },
          "property_type": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "oficina",
              "local_comercial",
              "terreno",
              "parcela",
              "bodega",
              "estacionamiento",
              "industrial",
              "otro"
            ],
            "description": "Tipo de propiedad."
          },
          "status": {
            "type": "string",
            "enum": [
              "disponible",
              "reservada",
              "vendida",
              "arrendada",
              "pausada"
            ],
            "description": "Estado comercial: disponible | reservada | vendida | arrendada | pausada. `pausada` takes it off the market and keeps it in the cartera."
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "sector": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 160
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "Dirección. PRIVATE: never shown to an AI agent; shared with a buyer once a visit is booked."
          },
          "sale_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Sale price, in `sale_price_currency`. Needs operation venta."
          },
          "sale_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "rent_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "Monthly rent, in `rent_price_currency`. Needs operation arriendo."
          },
          "rent_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "common_expenses_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Gastos comunes, CLP/mes."
          },
          "property_tax_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Contribuciones, CLP por cuota trimestral."
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros útiles."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 10000000,
            "description": "Metros totales."
          },
          "parking_spaces": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999
          },
          "storage_units": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 999,
            "description": "Bodegas."
          },
          "orientation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "norte",
              "sur",
              "oriente",
              "poniente",
              "nororiente",
              "norponiente",
              "suroriente",
              "surponiente"
            ]
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 300,
            "description": "Piso."
          },
          "year_built": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1800,
            "maximum": 2100
          },
          "furnished": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Amoblado."
          },
          "pets_allowed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Acepta mascotas."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Photo URLs in display order; the first is the cover."
          },
          "broker_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The corredor in charge (a member user id)."
          },
          "owner_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "Propietario. PRIVATE: never shown to an AI agent."
          },
          "owner_phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "owner_email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 320,
            "format": "email"
          }
        }
      },
      "PropertyPublication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "marketplace_account_id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The portal’s item id (MLC…)."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "imported",
              "published",
              "updating",
              "error",
              "deleted"
            ],
            "description": "imported = found on the portal; published = pushed by Vitrina; deleted = gone from the portal."
          },
          "origin": {
            "type": "string",
            "enum": [
              "vitrina",
              "portal"
            ]
          },
          "permalink": {
            "type": [
              "string",
              "null"
            ]
          },
          "listing_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "portal_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "listed",
              "paused",
              "withdrawn",
              "sold",
              "moderated",
              "unknown"
            ],
            "description": "What the portal says about the listing: listed | paused | withdrawn (closed) | moderated (under review) | unknown. Never changes the property’s status."
          },
          "portal_state_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "The portal’s own last-modified time for the listing."
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "property_id",
          "marketplace_account_id",
          "provider",
          "external_id",
          "status",
          "origin",
          "permalink",
          "listing_type",
          "portal_state",
          "portal_state_at",
          "last_synced_at",
          "last_published_at",
          "last_error",
          "created_at",
          "updated_at"
        ]
      },
      "PropertyPortalAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "provider": {
            "type": "string"
          },
          "portal": {
            "type": "string",
            "enum": [
              "portal_inmobiliario"
            ]
          },
          "name": {
            "type": "string"
          },
          "seller_nickname": {
            "type": [
              "string",
              "null"
            ],
            "description": "The Mercado Libre account the broker signed in with."
          },
          "enabled": {
            "type": "boolean"
          },
          "sync_enabled": {
            "type": "boolean"
          },
          "connected_at": {
            "type": "string"
          },
          "properties": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "listed": {
                "type": "integer"
              },
              "paused": {
                "type": "integer"
              },
              "other": {
                "type": "integer"
              }
            },
            "required": [
              "total",
              "listed",
              "paused",
              "other"
            ],
            "description": "Properties of the cartera with a listing on this account."
          },
          "last_run": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "status": {
                "type": "string"
              },
              "started_at": {
                "type": "string"
              },
              "finished_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "error_message": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "summary": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "seen": {
                    "type": "integer"
                  },
                  "created": {
                    "type": "integer"
                  },
                  "updated": {
                    "type": "integer"
                  },
                  "unchanged": {
                    "type": "integer"
                  },
                  "skipped": {
                    "type": "integer"
                  },
                  "not_real_estate": {
                    "type": "integer"
                  },
                  "removed_by_broker": {
                    "type": "integer"
                  },
                  "confirmed_absent": {
                    "type": "integer"
                  },
                  "portal_states": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer"
                    }
                  },
                  "truncated": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "seen",
                  "created",
                  "updated",
                  "unchanged",
                  "skipped",
                  "not_real_estate",
                  "removed_by_broker",
                  "confirmed_absent",
                  "portal_states",
                  "truncated"
                ],
                "description": "What the run did, listing by listing."
              }
            },
            "required": [
              "id",
              "status",
              "started_at",
              "finished_at",
              "error_message",
              "summary"
            ]
          }
        },
        "required": [
          "id",
          "provider",
          "portal",
          "name",
          "seller_nickname",
          "enabled",
          "sync_enabled",
          "connected_at",
          "properties",
          "last_run"
        ]
      },
      "PublishPropertyResult": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "published",
              "updated"
            ],
            "description": "published = a new listing was created; updated = the live listing was refreshed."
          },
          "publication": {
            "$ref": "#/components/schemas/PropertyPublication"
          }
        },
        "required": [
          "outcome",
          "publication"
        ]
      },
      "PropertyListingTier": {
        "type": "object",
        "properties": {
          "listing_type": {
            "type": "string",
            "description": "silver | gold | gold_premium"
          },
          "via": {
            "type": "string",
            "enum": [
              "publication",
              "upgrade"
            ],
            "description": "publication = a publication pack lists with it; upgrade = a destaque pack lifts a silver listing to it."
          },
          "remaining": {
            "type": "integer",
            "description": "Cupos left."
          }
        },
        "required": [
          "listing_type",
          "via",
          "remaining"
        ]
      },
      "ProjectListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "The proyecto’s site or sala address. Public: it is in the agent projection."
          },
          "stage": {
            "type": "string",
            "enum": [
              "en_blanco",
              "en_verde",
              "entrega_inmediata"
            ],
            "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
          },
          "delivery_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "recepcion_definitiva_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "on_call_user_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "subsidies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "DS19",
                "DS1",
                "DS49"
              ]
            }
          },
          "sales_office_location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "brochure_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta_ad_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "visor_show_prices": {
            "type": "boolean"
          },
          "whatsapp_code": {
            "type": "string",
            "description": "The proyecto’s code in its tracked WhatsApp link (two letters + three digits, generated, never edited). A first message that carries it ties the conversation and its lead to this proyecto before the agent replies. `GET /projects/{id}/whatsapp-links` builds the links."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "unit_model_count": {
            "type": "integer",
            "description": "Live tipologías of the proyecto."
          },
          "units_total": {
            "type": "integer",
            "description": "Live unidades of the proyecto, in any status."
          },
          "units_available": {
            "type": "integer",
            "description": "Unidades of the proyecto still `disponible`."
          },
          "price_from_uf": {
            "type": [
              "number",
              "null"
            ],
            "description": "The «desde» price: the lowest UF sale price among the `disponible` unidades (set by the published lista de precios). Null while none is priced in UF."
          },
          "member_count": {
            "type": "integer",
            "description": "Ejecutivos on the proyecto’s team."
          }
        },
        "required": [
          "id",
          "tenant_id",
          "name",
          "description",
          "region",
          "comuna",
          "address",
          "stage",
          "delivery_date",
          "recepcion_definitiva_at",
          "on_call_user_ids",
          "subsidies",
          "sales_office_location_id",
          "photos",
          "brochure_url",
          "meta_ad_ids",
          "visor_show_prices",
          "whatsapp_code",
          "created_by",
          "created_at",
          "updated_at",
          "unit_model_count",
          "units_total",
          "units_available",
          "price_from_uf",
          "member_count"
        ]
      },
      "UnitModel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Dormitorios."
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Baños."
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros útiles."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Metros de terraza."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros totales."
          },
          "floor_plans": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "renders": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "tour_360_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "project_id",
          "name",
          "description",
          "bedrooms",
          "bathrooms",
          "usable_area_m2",
          "terrace_area_m2",
          "total_area_m2",
          "floor_plans",
          "renders",
          "tour_360_url",
          "created_by",
          "created_at",
          "updated_at"
        ]
      },
      "Project": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "The proyecto’s site or sala address. Public: it is in the agent projection."
          },
          "stage": {
            "type": "string",
            "enum": [
              "en_blanco",
              "en_verde",
              "entrega_inmediata"
            ],
            "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
          },
          "delivery_date": {
            "type": [
              "string",
              "null"
            ]
          },
          "recepcion_definitiva_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "on_call_user_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "subsidies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "DS19",
                "DS1",
                "DS49"
              ]
            }
          },
          "sales_office_location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "brochure_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "meta_ad_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "visor_show_prices": {
            "type": "boolean"
          },
          "whatsapp_code": {
            "type": "string",
            "description": "The proyecto’s code in its tracked WhatsApp link (two letters + three digits, generated, never edited). A first message that carries it ties the conversation and its lead to this proyecto before the agent replies. `GET /projects/{id}/whatsapp-links` builds the links."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "sales_office": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "display_id",
              "name"
            ],
            "description": "The sala de ventas location, when one is set."
          },
          "unit_models": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnitModel"
            },
            "description": "Every live tipología of the proyecto, by name."
          }
        },
        "required": [
          "id",
          "tenant_id",
          "name",
          "description",
          "region",
          "comuna",
          "address",
          "stage",
          "delivery_date",
          "recepcion_definitiva_at",
          "on_call_user_ids",
          "subsidies",
          "sales_office_location_id",
          "photos",
          "brochure_url",
          "meta_ad_ids",
          "visor_show_prices",
          "whatsapp_code",
          "created_by",
          "created_at",
          "updated_at",
          "sales_office",
          "unit_models"
        ]
      },
      "CreateProjectBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "The proyecto’s site or sala address. Public: it is in the agent projection."
          },
          "stage": {
            "type": "string",
            "enum": [
              "en_blanco",
              "en_verde",
              "entrega_inmediata"
            ],
            "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
          },
          "delivery_date": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Fecha de entrega, `YYYY-MM-DD` (estimated while en blanco / en verde)."
          },
          "recepcion_definitiva_at": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Fecha de la recepción definitiva (DOM), `YYYY-MM-DD`. The 5-year instalaciones and 10-year estructura garantías of its post-venta casos (LGUC art. 18) start here."
          },
          "on_call_user_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 20,
            "description": "Members an emergencia post-venta caso on this proyecto is pushed to at once (bell + push). Each must be a member of this workspace. Empty = the caso's team."
          },
          "subsidies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "DS19",
                "DS1",
                "DS49"
              ]
            },
            "maxItems": 3,
            "description": "Housing subsidies the proyecto qualifies for: DS19, DS1, DS49. A guide, never a promise of eligibility."
          },
          "sales_office_location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The sala de ventas: the id of an active location (sucursal) of this workspace. One sala may serve several proyectos."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Photo URLs in display order; the first is the cover."
          },
          "brochure_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "format": "uri",
            "description": "Brochure URL (usually a PDF uploaded through /tenant/media)."
          },
          "meta_ad_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[0-9]{5,30}$"
            },
            "maxItems": 200,
            "description": "The Meta ads (Click-to-WhatsApp, or Instagram / Messenger «Enviar mensaje» ads) that sell this proyecto: a conversation one of them opens is tied to the proyecto before the agent replies, and its lead is about it. One ad sells at most one live proyecto (409 `meta_ad_already_mapped`). Optional: an unmapped ad is still recognised by the proyecto’s name in its campaign, ad set or ad names or in its copy."
          },
          "visor_show_prices": {
            "type": "boolean",
            "description": "Whether the public floor-plan visor shows the list price of the available unidades. Off by default."
          }
        },
        "required": [
          "name"
        ]
      },
      "UpdateProjectBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "region": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "comuna": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120
          },
          "address": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "The proyecto’s site or sala address. Public: it is in the agent projection."
          },
          "stage": {
            "type": "string",
            "enum": [
              "en_blanco",
              "en_verde",
              "entrega_inmediata"
            ],
            "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
          },
          "delivery_date": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Fecha de entrega, `YYYY-MM-DD` (estimated while en blanco / en verde)."
          },
          "recepcion_definitiva_at": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Fecha de la recepción definitiva (DOM), `YYYY-MM-DD`. The 5-year instalaciones and 10-year estructura garantías of its post-venta casos (LGUC art. 18) start here."
          },
          "on_call_user_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 20,
            "description": "Members an emergencia post-venta caso on this proyecto is pushed to at once (bell + push). Each must be a member of this workspace. Empty = the caso's team."
          },
          "subsidies": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "DS19",
                "DS1",
                "DS49"
              ]
            },
            "maxItems": 3,
            "description": "Housing subsidies the proyecto qualifies for: DS19, DS1, DS49. A guide, never a promise of eligibility."
          },
          "sales_office_location_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The sala de ventas: the id of an active location (sucursal) of this workspace. One sala may serve several proyectos."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Photo URLs in display order; the first is the cover."
          },
          "brochure_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "format": "uri",
            "description": "Brochure URL (usually a PDF uploaded through /tenant/media)."
          },
          "meta_ad_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^[0-9]{5,30}$"
            },
            "maxItems": 200,
            "description": "The Meta ads (Click-to-WhatsApp, or Instagram / Messenger «Enviar mensaje» ads) that sell this proyecto: a conversation one of them opens is tied to the proyecto before the agent replies, and its lead is about it. One ad sells at most one live proyecto (409 `meta_ad_already_mapped`). Optional: an unmapped ad is still recognised by the proyecto’s name in its campaign, ad set or ad names or in its copy."
          },
          "visor_show_prices": {
            "type": "boolean",
            "description": "Whether the public floor-plan visor shows the list price of the available unidades. Off by default."
          }
        }
      },
      "UnitModelListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/UnitModel"
          },
          {
            "type": "object",
            "properties": {
              "project": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  },
                  "comuna": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "stage": {
                    "type": "string",
                    "enum": [
                      "en_blanco",
                      "en_verde",
                      "entrega_inmediata"
                    ],
                    "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
                  }
                },
                "required": [
                  "id",
                  "name",
                  "comuna",
                  "stage"
                ],
                "description": "The proyecto it belongs to."
              }
            },
            "required": [
              "project"
            ]
          }
        ]
      },
      "CreateUnitModelBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "The proyecto this tipología belongs to."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "How the inmobiliaria names it, e.g. «3D2B 78 m²» or «Tipo A»."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Dormitorios."
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Baños."
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros útiles."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Metros de terraza."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros totales."
          },
          "floor_plans": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 30,
            "description": "Plantas: image or PDF URLs in display order."
          },
          "renders": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Render URLs in display order; the first is the cover."
          },
          "tour_360_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "format": "uri",
            "description": "Link to a 360° virtual tour."
          }
        },
        "required": [
          "project_id",
          "name"
        ]
      },
      "UpdateUnitModelBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "How the inmobiliaria names it, e.g. «3D2B 78 m²» or «Tipo A»."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Dormitorios."
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99,
            "description": "Baños."
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros útiles."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Metros de terraza."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros totales."
          },
          "floor_plans": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 30,
            "description": "Plantas: image or PDF URLs in display order."
          },
          "renders": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60,
            "description": "Render URLs in display order; the first is the cover."
          },
          "tour_360_url": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "format": "uri",
            "description": "Link to a 360° virtual tour."
          }
        },
        "additionalProperties": false
      },
      "ProjectWhatsappLinks": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "whatsapp_code": {
            "type": "string"
          },
          "prefill_text": {
            "type": "string",
            "description": "The message the link prefills, carrying the proyecto’s code. The buyer sends it as their first message."
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "messaging_account_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string",
                  "description": "The WhatsApp channel’s name."
                },
                "phone_e164": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "`https://wa.me/<number>?text=<prefill>`, ready to share."
                }
              },
              "required": [
                "messaging_account_id",
                "name",
                "phone_e164",
                "url"
              ]
            },
            "description": "One link per enabled WhatsApp number of the workspace; empty while none is connected."
          }
        },
        "required": [
          "project_id",
          "whatsapp_code",
          "prefill_text",
          "links"
        ]
      },
      "ProjectListing": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "marketplace_account_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": "string"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "permalink": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "marketplace_account_id",
          "external_id",
          "project_id",
          "title",
          "permalink",
          "created_at",
          "updated_at"
        ],
        "description": "A Portal Inmobiliario proyecto listing (anuncio) and the proyecto it maps to. `project_id` null = not mapped yet."
      },
      "ProjectListingListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProjectListing"
          },
          {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string",
                "description": "The portal the anuncio is on: `mercadolibre` or `yapo`."
              },
              "lead_count": {
                "type": "integer",
                "description": "Leads that arrived on the anuncio."
              },
              "unmapped_lead_count": {
                "type": "integer",
                "description": "Of those, the ones still without a proyecto."
              }
            },
            "required": [
              "provider",
              "lead_count",
              "unmapped_lead_count"
            ]
          }
        ],
        "description": "A Portal Inmobiliario proyecto listing (anuncio) and the proyecto it maps to. `project_id` null = not mapped yet."
      },
      "MappedProjectListing": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProjectListing"
          },
          {
            "type": "object",
            "properties": {
              "leads_mapped": {
                "type": "integer",
                "description": "Leads that received the proyecto through this mapping."
              }
            },
            "required": [
              "leads_mapped"
            ]
          }
        ],
        "description": "A Portal Inmobiliario proyecto listing (anuncio) and the proyecto it maps to. `project_id` null = not mapped yet."
      },
      "CreateProjectListingBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "The proyecto the anuncio belongs to."
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80,
            "description": "The portal’s listing id: the Mercado Libre item id (e.g. `MLC1234567890`) or the Yapo ad id (`adid`)."
          },
          "provider": {
            "type": "string",
            "enum": [
              "mercadolibre",
              "yapo"
            ],
            "description": "The portal the anuncio is on (default `mercadolibre`). Picks the workspace’s only connection of that portal when `marketplace_account_id` is omitted."
          },
          "marketplace_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The Mercado Libre or Yapo connection. Optional when the workspace has exactly one of the portal named by `provider`."
          },
          "title": {
            "type": "string",
            "maxLength": 300,
            "description": "The anuncio’s title, for display."
          }
        },
        "required": [
          "project_id",
          "external_id"
        ],
        "additionalProperties": false
      },
      "UpdateProjectListingBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The proyecto to map the anuncio to, or `null` to unmap it. Mapping stamps the proyecto on the leads already waiting on the anuncio and routes the unowned ones to its ejecutivos."
          }
        },
        "required": [
          "project_id"
        ],
        "additionalProperties": false
      },
      "ProjectPublishReadiness": {
        "type": "object",
        "properties": {
          "ready": {
            "type": "boolean"
          },
          "field_errors": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "What the portal would refuse, keyed by the field to fix: a proyecto field (`name`, `stage`, `common_expenses_clp`), `models.<tipología>.<field>`, `price`, `comuna`, `photos` or `seller_contact`."
          },
          "models_listed": {
            "type": "integer",
            "description": "Tipologías that would be listed."
          },
          "units_available": {
            "type": "integer",
            "description": "Disponible unidades across them."
          },
          "price_from_uf": {
            "type": [
              "number",
              "null"
            ],
            "description": "The «desde» price in UF (published lista de precios)."
          }
        },
        "required": [
          "ready",
          "field_errors",
          "models_listed",
          "units_available",
          "price_from_uf"
        ]
      },
      "ProjectPublication": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "marketplace_account_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "provider": {
            "type": "string"
          },
          "external_id": {
            "type": "string",
            "description": "The portal’s listing id (anuncio)."
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "permalink": {
            "type": [
              "string",
              "null"
            ]
          },
          "origin": {
            "type": "string",
            "enum": [
              "vitrina",
              "portal"
            ],
            "description": "vitrina = published from here; portal = created on the portal."
          },
          "status": {
            "type": "string",
            "enum": [
              "imported",
              "published",
              "error",
              "deleted"
            ],
            "description": "Vitrina’s lifecycle for the listing; error keeps the live listing and `last_error` says why the last push failed."
          },
          "listing_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "portal_state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "listed",
              "paused",
              "withdrawn",
              "sold",
              "moderated",
              "unknown"
            ],
            "description": "Estado del portal. Recording only: no state of the portal ever changes a unidad’s status."
          },
          "portal_state_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paused_by_vitrina": {
            "type": "boolean",
            "description": "True while the pause is Vitrina’s (no unidad disponible). Only that pause is lifted when stock returns."
          },
          "tipologias_listed": {
            "type": "integer",
            "description": "Tipologías that are variations of the listing on the portal."
          },
          "last_published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "marketplace_account_id",
          "project_id",
          "provider",
          "external_id",
          "title",
          "permalink",
          "origin",
          "status",
          "listing_type",
          "portal_state",
          "portal_state_at",
          "paused_by_vitrina",
          "tipologias_listed",
          "last_published_at",
          "last_synced_at",
          "last_error",
          "created_at",
          "updated_at"
        ]
      },
      "PublishProjectResult": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "published",
              "updated"
            ],
            "description": "published = a new listing was created; updated = the live listing was refreshed."
          },
          "publication": {
            "$ref": "#/components/schemas/ProjectPublication"
          }
        },
        "required": [
          "outcome",
          "publication"
        ]
      },
      "PublishProjectBody": {
        "type": "object",
        "properties": {
          "account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The Portal Inmobiliario account to publish on."
          },
          "listing_type": {
            "type": "string",
            "enum": [
              "silver",
              "gold",
              "gold_premium"
            ],
            "description": "Listing tier. Omit it: a developments pack lists with the tier it names (default gold_premium)."
          }
        },
        "required": [
          "account_id"
        ],
        "additionalProperties": false
      },
      "ChannelPartner": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "broker",
              "referrer"
            ],
            "description": "`broker` = corredor externo (a broker outside the inmobiliaria); `referrer` = referidor (often an owner who refers a buyer)."
          },
          "name": {
            "type": "string"
          },
          "company": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "code": {
            "type": "string",
            "description": "The personal code of their link: `?ref=<code>`."
          },
          "referral_param": {
            "type": "string",
            "description": "The query parameter to append to a proyecto page or form URL, e.g. `ref=maria4k2x`. The proyecto form script forwards it as `channel_code` to `POST /website-leads`."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ],
            "description": "A paused partner cannot register or be credited new buyers."
          },
          "protection_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 730,
            "description": "Days a buyer they bring stays credited to them (default 90). A second partner claiming the same buyer on the same proyecto inside the window is refused."
          },
          "commission_basis": {
            "type": "string",
            "enum": [
              "percent",
              "fixed"
            ],
            "description": "`percent` = a percentage of the negocio value; `fixed` = a fixed amount in `commission_currency`."
          },
          "commission_value": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000,
            "description": "The percentage (0 to 100) for `percent`, or the amount for `fixed`."
          },
          "commission_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ],
            "description": "Required for `fixed`. For `percent` it may pin the currency; left null the currency of the negocio value is used."
          },
          "commission_milestone": {
            "type": "string",
            "enum": [
              "reserva",
              "promesa",
              "escritura",
              "entrega"
            ],
            "description": "The negocio milestone at which the commission falls due."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "kind",
          "name",
          "company",
          "email",
          "phone",
          "code",
          "referral_param",
          "status",
          "protection_days",
          "commission_basis",
          "commission_value",
          "commission_currency",
          "commission_milestone",
          "notes",
          "created_by",
          "created_at",
          "updated_at"
        ],
        "description": "A corredor externo or referidor with the code of their personal link and the commission agreed."
      },
      "ChannelPartnerListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ChannelPartner"
          },
          {
            "type": "object",
            "properties": {
              "active_claim_count": {
                "type": "integer",
                "description": "Buyers currently credited to them (inside their window)."
              },
              "due_commission_count": {
                "type": "integer",
                "description": "Commissions whose milestone was reached and are unpaid."
              }
            },
            "required": [
              "active_claim_count",
              "due_commission_count"
            ]
          }
        ],
        "description": "A corredor externo or referidor with the code of their personal link and the commission agreed."
      },
      "CreateChannelPartnerBody": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "broker",
              "referrer"
            ],
            "description": "`broker` = corredor externo (a broker outside the inmobiliaria); `referrer` = referidor (often an owner who refers a buyer)."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 254,
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "protection_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 730,
            "description": "Days a buyer they bring stays credited to them (default 90). A second partner claiming the same buyer on the same proyecto inside the window is refused."
          },
          "commission_basis": {
            "type": "string",
            "enum": [
              "percent",
              "fixed"
            ],
            "description": "`percent` = a percentage of the negocio value; `fixed` = a fixed amount in `commission_currency`."
          },
          "commission_value": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000,
            "description": "The percentage (0 to 100) for `percent`, or the amount for `fixed`."
          },
          "commission_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ],
            "description": "Required for `fixed`. For `percent` it may pin the currency; left null the currency of the negocio value is used."
          },
          "commission_milestone": {
            "type": "string",
            "enum": [
              "reserva",
              "promesa",
              "escritura",
              "entrega"
            ],
            "description": "The negocio milestone at which the commission falls due."
          }
        },
        "required": [
          "kind",
          "name"
        ],
        "additionalProperties": false
      },
      "UpdateChannelPartnerBody": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "broker",
              "referrer"
            ],
            "description": "`broker` = corredor externo (a broker outside the inmobiliaria); `referrer` = referidor (often an owner who refers a buyer)."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 254,
            "format": "email"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ],
            "description": "A paused partner cannot register or be credited new buyers."
          },
          "protection_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 730,
            "description": "Days a buyer they bring stays credited to them (default 90). A second partner claiming the same buyer on the same proyecto inside the window is refused."
          },
          "commission_basis": {
            "type": "string",
            "enum": [
              "percent",
              "fixed"
            ],
            "description": "`percent` = a percentage of the negocio value; `fixed` = a fixed amount in `commission_currency`."
          },
          "commission_value": {
            "type": "number",
            "minimum": 0,
            "maximum": 1000000000,
            "description": "The percentage (0 to 100) for `percent`, or the amount for `fixed`."
          },
          "commission_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ],
            "description": "Required for `fixed`. For `percent` it may pin the currency; left null the currency of the negocio value is used."
          },
          "commission_milestone": {
            "type": "string",
            "enum": [
              "reserva",
              "promesa",
              "escritura",
              "entrega"
            ],
            "description": "The negocio milestone at which the commission falls due."
          }
        },
        "additionalProperties": false
      },
      "ChannelClaim": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "refused"
            ],
            "description": "`active` = credited, protected until `protected_until`; `refused` = lost to an earlier claim still inside its window (kept for the audit trail)."
          },
          "claimed_at": {
            "type": "string"
          },
          "protected_until": {
            "type": "string"
          },
          "refused_by_claim_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "partner_id",
          "contact_id",
          "lead_id",
          "project_id",
          "status",
          "claimed_at",
          "protected_until",
          "refused_by_claim_id",
          "created_at"
        ],
        "description": "A partner’s claim on a buyer for a proyecto."
      },
      "ChannelCommission": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "partner_id": {
            "type": "string",
            "format": "uuid"
          },
          "claim_id": {
            "type": "string",
            "format": "uuid"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "basis": {
            "type": "string",
            "enum": [
              "percent",
              "fixed"
            ],
            "description": "`percent` = a percentage of the negocio value; `fixed` = a fixed amount in `commission_currency`."
          },
          "rate_value": {
            "type": "number",
            "description": "The agreed percentage or fixed amount, frozen at claim time."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "milestone": {
            "type": "string",
            "enum": [
              "reserva",
              "promesa",
              "escritura",
              "entrega"
            ],
            "description": "The negocio milestone at which the commission falls due."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "due",
              "paid",
              "cancelled"
            ],
            "description": "`pending` = the milestone is not reached; `due` = reached, to be paid; `paid`; `cancelled` = desistimiento."
          },
          "base_amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "The negocio value a percentage was applied to."
          },
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "description": "The commission owed once due. Null while pending, or due without a negocio value yet."
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "partner_id",
          "claim_id",
          "lead_id",
          "project_id",
          "basis",
          "rate_value",
          "currency",
          "milestone",
          "status",
          "base_amount",
          "amount",
          "due_at",
          "paid_at",
          "paid_reference",
          "cancelled_reason",
          "created_at",
          "updated_at"
        ],
        "description": "The commission or reward a credited lead earns its partner, tracked to payout."
      },
      "ChannelClaimResult": {
        "type": "object",
        "properties": {
          "outcome": {
            "type": "string",
            "enum": [
              "credited",
              "already_credited"
            ],
            "description": "`credited` = new claim; `already_credited` = the same partner already holds this buyer inside their window (nothing changes)."
          },
          "claim": {
            "$ref": "#/components/schemas/ChannelClaim"
          },
          "commission": {
            "$ref": "#/components/schemas/ChannelCommission"
          }
        },
        "required": [
          "outcome",
          "claim",
          "commission"
        ]
      },
      "ClaimChannelLeadBody": {
        "type": "object",
        "properties": {
          "lead_id": {
            "type": "string",
            "format": "uuid",
            "description": "The lead whose buyer the partner brought."
          }
        },
        "required": [
          "lead_id"
        ],
        "additionalProperties": false
      },
      "RecordChannelMilestoneBody": {
        "type": "object",
        "properties": {
          "lead_id": {
            "type": "string",
            "format": "uuid"
          },
          "milestone": {
            "type": "string",
            "enum": [
              "reserva",
              "promesa",
              "escritura",
              "entrega"
            ],
            "description": "The negocio milestone at which the commission falls due."
          },
          "base_amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 1000000000000,
            "description": "The negocio value a percentage commission applies to. Defaults to the lead’s value. Send it again to fill in the amount of a commission that fell due without one."
          },
          "base_currency": {
            "type": "string",
            "enum": [
              "UF",
              "CLP"
            ]
          }
        },
        "required": [
          "lead_id",
          "milestone"
        ],
        "additionalProperties": false
      },
      "PayChannelCommissionBody": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string",
            "maxLength": 200,
            "description": "Transfer or receipt reference of the payout."
          }
        },
        "additionalProperties": false
      },
      "CancelChannelCommissionBody": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "maxLength": 500
          }
        },
        "additionalProperties": false
      },
      "ToctocConnection": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "token_hint": {
            "type": "string",
            "description": "The last four characters of the token."
          },
          "last_lead_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "token_hint",
          "last_lead_at",
          "created_at",
          "updated_at"
        ],
        "description": "The workspace’s inbound TocToc lead door."
      },
      "ToctocConnectionStatus": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "connection": {
            "$ref": "#/components/schemas/ToctocConnection"
          },
          "lead_path": {
            "type": "string",
            "description": "The path the door is served on; append the token to get the URL a forwarded TocToc notification is POSTed to."
          }
        },
        "required": [
          "connected",
          "connection",
          "lead_path"
        ]
      },
      "ToctocToken": {
        "type": "object",
        "properties": {
          "connection": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ToctocConnection"
              },
              {
                "type": "object"
              }
            ]
          },
          "token": {
            "type": "string",
            "description": "The URL token, shown ONCE (only its hash is kept). Rotating it stops the previous URL."
          },
          "path": {
            "type": "string",
            "description": "`lead_path` + `/` + the token."
          }
        },
        "required": [
          "connection",
          "token",
          "path"
        ]
      },
      "ToctocListing": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_id": {
            "type": "string",
            "description": "TocToc’s publication code, or `name:<proyecto name>` when a notification carried only a name."
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "external_id",
          "project_id",
          "title",
          "created_at",
          "updated_at"
        ],
        "description": "A TocToc publication and the proyecto it maps to."
      },
      "ToctocListingListItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ToctocListing"
          },
          {
            "type": "object",
            "properties": {
              "lead_count": {
                "type": "integer"
              },
              "unmapped_lead_count": {
                "type": "integer"
              }
            },
            "required": [
              "lead_count",
              "unmapped_lead_count"
            ]
          }
        ],
        "description": "A TocToc publication and the proyecto it maps to."
      },
      "MappedToctocListing": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ToctocListing"
          },
          {
            "type": "object",
            "properties": {
              "leads_mapped": {
                "type": "integer"
              }
            },
            "required": [
              "leads_mapped"
            ]
          }
        ],
        "description": "A TocToc publication and the proyecto it maps to."
      },
      "CreateToctocListingBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "The proyecto the publication belongs to."
          },
          "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "The code TocToc gave the publication."
          },
          "title": {
            "type": "string",
            "maxLength": 300
          }
        },
        "required": [
          "project_id",
          "external_id"
        ],
        "additionalProperties": false
      },
      "UpdateToctocListingBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The proyecto to map the publication to, or `null` to unmap it. Mapping stamps the proyecto on the leads waiting on it and routes the unowned ones to its ejecutivos."
          }
        },
        "required": [
          "project_id"
        ],
        "additionalProperties": false
      },
      "ToctocPublication": {
        "type": [
          "object",
          "null"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "submitted",
              "live",
              "paused"
            ],
            "description": "`draft` = nothing handed to TocToc; `submitted` = handed over; `live` = TocToc confirmed it is published (needs `external_ref`); `paused`."
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "submitted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "live_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "tenant_id",
          "project_id",
          "status",
          "external_ref",
          "notes",
          "submitted_at",
          "live_at",
          "created_at",
          "updated_at"
        ],
        "description": "Null while nothing was recorded for the proyecto (a draft)."
      },
      "ToctocProjectPayload": {
        "type": "object",
        "properties": {
          "schema": {
            "type": "string",
            "enum": [
              "vitrina.toctoc.project.v1"
            ]
          },
          "project": {
            "type": "object",
            "properties": {
              "reference": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "stage": {
                "type": "string"
              },
              "stage_label": {
                "type": "string"
              },
              "delivery_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "region": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comuna": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "subsidies": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "photos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "brochure_url": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "reference",
              "name",
              "description",
              "stage",
              "stage_label",
              "delivery_date",
              "region",
              "comuna",
              "address",
              "subsidies",
              "photos",
              "brochure_url"
            ]
          },
          "unit_models": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "reference": {
                  "type": "string",
                  "format": "uuid"
                },
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "bedrooms": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "bathrooms": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "usable_area_m2": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "terrace_area_m2": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "total_area_m2": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "floor_plans": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "renders": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "tour_360_url": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "total_units": {
                  "type": "integer"
                },
                "available_units": {
                  "type": "integer"
                },
                "price": {
                  "type": [
                    "object",
                    "null"
                  ],
                  "properties": {
                    "currency": {
                      "type": "string"
                    },
                    "from": {
                      "type": "number"
                    },
                    "to": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "currency",
                    "from",
                    "to"
                  ]
                }
              },
              "required": [
                "reference",
                "name",
                "description",
                "bedrooms",
                "bathrooms",
                "usable_area_m2",
                "terrace_area_m2",
                "total_area_m2",
                "floor_plans",
                "renders",
                "tour_360_url",
                "total_units",
                "available_units",
                "price"
              ]
            }
          },
          "extras": {
            "type": "object",
            "properties": {
              "parking_available": {
                "type": "integer"
              },
              "storage_available": {
                "type": "integer"
              }
            },
            "required": [
              "parking_available",
              "storage_available"
            ]
          }
        },
        "required": [
          "schema",
          "project",
          "unit_models",
          "extras"
        ],
        "description": "What a hand-over of the proyecto to TocToc carries: the proyecto, its tipologías and the live stock behind each. Versioned by `schema`."
      },
      "ToctocProjectPublication": {
        "type": "object",
        "properties": {
          "publication": {
            "$ref": "#/components/schemas/ToctocPublication"
          },
          "payload": {
            "$ref": "#/components/schemas/ToctocProjectPayload"
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              },
              "required": [
                "field",
                "message"
              ]
            },
            "description": "What the listing is still missing (Spanish, for the person publishing). Empty = ready to hand over."
          }
        },
        "required": [
          "publication",
          "payload",
          "issues"
        ]
      },
      "UpdateToctocPublicationBody": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "submitted",
              "live",
              "paused"
            ],
            "description": "`draft` = nothing handed to TocToc; `submitted` = handed over; `live` = TocToc confirmed it is published (needs `external_ref`); `paused`."
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 120,
            "description": "The code TocToc gave the proyecto. Recording it maps it, so leads that carry it land on the proyecto."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          }
        },
        "additionalProperties": false
      },
      "Unit": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Property"
          },
          {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "disponible",
                  "bloqueada",
                  "reservada",
                  "promesada",
                  "vendida",
                  "arrendada",
                  "pausada"
                ],
                "description": "Estado: disponible | bloqueada | reservada | promesada | vendida | pausada (arrendada never occurs on a unidad)."
              },
              "project_id": {
                "type": "string",
                "format": "uuid"
              },
              "unit_number": {
                "type": "string"
              },
              "kind": {
                "type": "string",
                "enum": [
                  "departamento",
                  "casa",
                  "estacionamiento",
                  "bodega"
                ],
                "description": "departamento | casa | estacionamiento | bodega"
              },
              "project": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              },
              "unit_model": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "name"
                ]
              }
            },
            "required": [
              "kind",
              "project",
              "unit_model"
            ]
          }
        ]
      },
      "CreateUnitBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "unit_model_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A tipología of the SAME proyecto (`GET /unit-models`)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "estacionamiento",
              "bodega"
            ],
            "description": "departamento | casa | estacionamiento | bodega"
          },
          "unit_number": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "description": "The unidad’s number as the inmobiliaria writes it (1203, E-12, B-4). Unique per proyecto and kind."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Default «Departamento 1203 · Proyecto»; follows the number and kind until a person writes one."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 300,
            "description": "Piso (negative: subterráneo)."
          },
          "orientation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "norte",
              "sur",
              "oriente",
              "poniente",
              "nororiente",
              "norponiente",
              "suroriente",
              "surponiente"
            ]
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros útiles."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Metros de terraza."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros totales."
          },
          "sale_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "List price, in `sale_price_currency`."
          },
          "sale_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "common_expenses_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Gastos comunes estimados, CLP/mes."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60
          }
        },
        "required": [
          "project_id",
          "kind",
          "unit_number"
        ]
      },
      "UnitImportColumns": {
        "type": "object",
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "csv",
              "xlsx",
              "xls"
            ]
          },
          "sheet_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "header": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The file’s column names."
          },
          "row_count": {
            "type": "integer"
          },
          "sample": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "The first five rows, cell by cell in header order."
          },
          "suggested_mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Field → column we recognised by name."
          },
          "ignored_status_columns": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Columns that look like a status (Estado…). Never imported: only the negocio moves a unidad’s status."
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "unit_number",
                    "kind",
                    "unit_model",
                    "floor",
                    "orientation",
                    "bedrooms",
                    "bathrooms",
                    "usable_area_m2",
                    "terrace_area_m2",
                    "total_area_m2",
                    "price_uf",
                    "description"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "label",
                "required",
                "description"
              ]
            },
            "description": "Every field a column can feed."
          }
        },
        "required": [
          "format",
          "sheet_name",
          "header",
          "row_count",
          "sample",
          "suggested_mapping",
          "ignored_status_columns",
          "fields"
        ]
      },
      "UnitImportReport": {
        "type": "object",
        "properties": {
          "dry_run": {
            "type": "boolean"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "The mapping applied."
          },
          "ignored_status_columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "ignored_price_column": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mapped price column, left out because the proyecto has a published lista de precios: its unidades’ prices change only by publishing a new version (`POST /price-lists`)."
          },
          "summary": {
            "type": "object",
            "properties": {
              "total_rows": {
                "type": "integer"
              },
              "create": {
                "type": "integer"
              },
              "update": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "error": {
                "type": "integer"
              },
              "not_in_file": {
                "type": "integer",
                "description": "Live unidades of the proyecto the file does not mention. An import never removes a unidad."
              }
            },
            "required": [
              "total_rows",
              "create",
              "update",
              "unchanged",
              "error",
              "not_in_file"
            ]
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row_number": {
                  "type": "integer",
                  "description": "The row in the sheet (header = 1), as the spreadsheet shows it."
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "create",
                    "update",
                    "unchanged",
                    "error"
                  ],
                  "description": "What this row does (dry run) or did (commit)."
                },
                "unit_number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "kind": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "departamento",
                    "casa",
                    "estacionamiento",
                    "bodega"
                  ],
                  "description": "departamento | casa | estacionamiento | bodega"
                },
                "unit_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "display_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "fields": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "create: the fields the file sets; update: the fields it changes."
                },
                "protected_skipped": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Fields the file would change but a person edited by hand: kept (release them with `POST /units/{id}/release-fields`)."
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "disponible",
                    "bloqueada",
                    "reservada",
                    "promesada",
                    "vendida",
                    "arrendada",
                    "pausada"
                  ],
                  "description": "The status the unidad keeps (an import never changes it) or is born with."
                },
                "errors": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "field": {
                        "type": "string"
                      },
                      "message": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "field",
                      "message"
                    ]
                  }
                }
              },
              "required": [
                "row_number",
                "action",
                "unit_number",
                "kind",
                "unit_id",
                "display_id",
                "fields",
                "protected_skipped",
                "status",
                "errors"
              ]
            }
          }
        },
        "required": [
          "dry_run",
          "project_id",
          "mapping",
          "ignored_status_columns",
          "ignored_price_column",
          "summary",
          "rows"
        ]
      },
      "ImportUnitsBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "The proyecto the unidades belong to."
          },
          "file": {
            "description": "Multipart only: the .xlsx, .xls or .csv (first sheet with data; the first non-empty row is the header)."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "maxItems": 2000,
            "description": "JSON only, instead of a file: one object per unidad, keyed by column name (`{ \"Número\": \"1203\", \"Piso\": 12, \"Tipología\": \"3D2B\" }`). Map with `mapping`, or use field names as keys."
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 200
            },
            "description": "Field → column name of the file (the header `POST /units/import/columns` returns). A field left out (or null) is not imported. Omitted entirely: the suggested mapping. There is NO status field: an import never writes a unidad’s status."
          },
          "default_kind": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "estacionamiento",
              "bodega"
            ],
            "description": "The kind of a row with no kind (default departamento). Set `estacionamiento` to load a sheet of estacionamientos."
          },
          "dry_run": {
            "type": "boolean",
            "description": "Default TRUE: report what would happen and write nothing. Send false to write."
          }
        },
        "required": [
          "project_id"
        ]
      },
      "UpdateUnitBody": {
        "type": "object",
        "properties": {
          "unit_model_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "A tipología of the SAME proyecto (`GET /unit-models`)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "estacionamiento",
              "bodega"
            ],
            "description": "departamento | casa | estacionamiento | bodega"
          },
          "unit_number": {
            "type": "string",
            "minLength": 1,
            "maxLength": 40,
            "description": "The unidad’s number as the inmobiliaria writes it (1203, E-12, B-4). Unique per proyecto and kind."
          },
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Default «Departamento 1203 · Proyecto»; follows the number and kind until a person writes one."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 300,
            "description": "Piso (negative: subterráneo)."
          },
          "orientation": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "norte",
              "sur",
              "oriente",
              "poniente",
              "nororiente",
              "norponiente",
              "suroriente",
              "surponiente"
            ]
          },
          "bedrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99
          },
          "bathrooms": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 99
          },
          "usable_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros útiles."
          },
          "terrace_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000,
            "description": "Metros de terraza."
          },
          "total_area_m2": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000,
            "description": "Metros totales."
          },
          "sale_price_amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "description": "List price, in `sale_price_currency`."
          },
          "sale_price_currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "common_expenses_clp": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0,
            "maximum": 1000000000,
            "description": "Gastos comunes estimados, CLP/mes."
          },
          "photos": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 2000,
              "format": "uri"
            },
            "maxItems": 60
          },
          "status": {
            "type": "string",
            "enum": [
              "disponible",
              "pausada"
            ],
            "description": "A person may only pause a unidad and put it back: disponible ⇄ pausada, and only while no negocio holds it. Every other move is the negocio’s."
          }
        }
      },
      "DatahunterImportColumns": {
        "type": "object",
        "properties": {
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "entity": {
                  "type": "string",
                  "enum": [
                    "proyectos",
                    "unidades",
                    "leads",
                    "propietarios",
                    "cotizaciones",
                    "negocios"
                  ],
                  "description": "proyectos | unidades | leads | propietarios | cotizaciones | negocios"
                },
                "format": {
                  "type": "string"
                },
                "sheet_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "header": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "row_count": {
                  "type": "integer"
                },
                "sample": {
                  "type": "array",
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "suggested_mapping": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "ignored_status_columns": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "Unidades file only: columns that look like a status. The stock import never reads them; a negocio or owner raises the status."
                },
                "fields": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key": {
                        "type": "string"
                      },
                      "label": {
                        "type": "string"
                      },
                      "required": {
                        "type": "boolean"
                      },
                      "description": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "key",
                      "label",
                      "required",
                      "description"
                    ]
                  }
                }
              },
              "required": [
                "entity",
                "format",
                "sheet_name",
                "header",
                "row_count",
                "sample",
                "suggested_mapping",
                "ignored_status_columns",
                "fields"
              ]
            }
          }
        },
        "required": [
          "files"
        ]
      },
      "DatahunterImportReport": {
        "type": "object",
        "properties": {
          "dry_run": {
            "type": "boolean"
          },
          "step": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "proyectos",
              "unidades",
              "leads",
              "propietarios",
              "cotizaciones",
              "negocios"
            ],
            "description": "proyectos | unidades | leads | propietarios | cotizaciones | negocios"
          },
          "batch_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": "string"
              }
            }
          },
          "ignored_status_columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pipeline": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "entities": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "summary": {
                  "type": "object",
                  "properties": {
                    "source_rows": {
                      "type": "integer"
                    },
                    "create": {
                      "type": "integer"
                    },
                    "update": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "skipped": {
                      "type": "integer"
                    },
                    "error": {
                      "type": "integer"
                    },
                    "balanced": {
                      "type": "boolean",
                      "description": "create + update + unchanged + skipped + error = source_rows."
                    }
                  },
                  "required": [
                    "source_rows",
                    "create",
                    "update",
                    "unchanged",
                    "skipped",
                    "error",
                    "balanced"
                  ]
                },
                "issues": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "row_number": {
                        "type": "integer"
                      },
                      "action": {
                        "type": "string",
                        "enum": [
                          "create",
                          "update",
                          "unchanged",
                          "skipped",
                          "error"
                        ]
                      },
                      "label": {
                        "type": "string"
                      },
                      "errors": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field",
                            "message"
                          ]
                        }
                      },
                      "warnings": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field",
                            "message"
                          ]
                        }
                      },
                      "notes": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "required": [
                      "row_number",
                      "action",
                      "label",
                      "errors",
                      "warnings",
                      "notes"
                    ]
                  }
                },
                "issues_truncated": {
                  "type": "boolean"
                },
                "sample": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "row_number": {
                        "type": "integer"
                      },
                      "action": {
                        "type": "string",
                        "enum": [
                          "create",
                          "update",
                          "unchanged",
                          "skipped",
                          "error"
                        ]
                      },
                      "label": {
                        "type": "string"
                      },
                      "errors": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field",
                            "message"
                          ]
                        }
                      },
                      "warnings": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "message": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field",
                            "message"
                          ]
                        }
                      },
                      "notes": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      }
                    },
                    "required": [
                      "row_number",
                      "action",
                      "label",
                      "errors",
                      "warnings",
                      "notes"
                    ]
                  }
                }
              },
              "required": [
                "summary",
                "issues",
                "issues_truncated",
                "sample"
              ]
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "source_rows": {
                "type": "integer"
              },
              "create": {
                "type": "integer"
              },
              "update": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "skipped": {
                "type": "integer"
              },
              "error": {
                "type": "integer"
              },
              "balanced": {
                "type": "boolean",
                "description": "create + update + unchanged + skipped + error = source_rows."
              }
            },
            "required": [
              "source_rows",
              "create",
              "update",
              "unchanged",
              "skipped",
              "error",
              "balanced"
            ]
          },
          "contacts": {
            "type": "object",
            "properties": {
              "people": {
                "type": "integer"
              },
              "create": {
                "type": "integer"
              },
              "match_existing": {
                "type": "integer"
              },
              "merged_in_file": {
                "type": "integer"
              }
            },
            "required": [
              "people",
              "create",
              "match_existing",
              "merged_in_file"
            ]
          },
          "unit_models_to_create": {
            "type": "integer"
          },
          "unit_status_changes": {
            "type": "object",
            "properties": {
              "reservada": {
                "type": "integer"
              },
              "promesada": {
                "type": "integer"
              },
              "vendida": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              }
            },
            "required": [
              "reservada",
              "promesada",
              "vendida",
              "unchanged"
            ]
          },
          "lead_stages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "value": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                },
                "stage_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "stage_name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "how": {
                  "type": "string",
                  "enum": [
                    "chosen",
                    "same_name",
                    "entry"
                  ]
                }
              },
              "required": [
                "value",
                "count",
                "stage_id",
                "stage_name",
                "how"
              ]
            }
          },
          "negocios_by_stage": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            }
          },
          "staged": {
            "type": "object",
            "properties": {
              "cotizacion": {
                "type": "integer"
              },
              "negocio": {
                "type": "integer"
              }
            },
            "required": [
              "cotizacion",
              "negocio"
            ]
          },
          "written": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "integer"
            },
            "description": "A commit only: what was written."
          }
        },
        "required": [
          "dry_run",
          "step",
          "batch_ids",
          "mapping",
          "ignored_status_columns",
          "pipeline",
          "entities",
          "totals",
          "contacts",
          "unit_models_to_create",
          "unit_status_changes",
          "lead_stages",
          "negocios_by_stage",
          "staged",
          "written"
        ]
      },
      "RunDatahunterImportBody": {
        "type": "object",
        "properties": {
          "rows": {
            "type": "object",
            "properties": {
              "proyectos": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              },
              "unidades": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              },
              "leads": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              },
              "propietarios": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              },
              "cotizaciones": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              },
              "negocios": {
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": {
                    "anyOf": [
                      {
                        "type": "string"
                      },
                      {
                        "type": "number"
                      },
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "null"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "maxItems": 5000
              }
            },
            "description": "JSON only, instead of files: one object per row, keyed by column name or by field key (what the #4806 extractor writes). Up to 5000 rows per file."
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 200
              }
            },
            "description": "Per file: field → column name. A field left out is not imported; a file left out of the mapping uses the suggested one. `GET`-less: `POST /datahunter-import/columns` returns each file’s fields and suggestion."
          },
          "pipeline_id": {
            "type": "string",
            "format": "uuid",
            "description": "The pipeline imported leads land in. Default: the workspace’s sales pipeline."
          },
          "stage_mapping": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ],
              "format": "uuid"
            },
            "description": "Datahunter etapa (lowercase, no accents) → stage id of that pipeline. The dry run lists each etapa in `lead_stages`; an etapa with the same name as a stage maps to it, any other goes to the first stage."
          },
          "step": {
            "type": "string",
            "enum": [
              "proyectos",
              "unidades",
              "leads",
              "propietarios",
              "cotizaciones",
              "negocios"
            ],
            "description": "Run only this file. A screen runs the files one by one, in the order proyectos, unidades, leads, propietarios, cotizaciones, negocios, to show progress. Without it a commit runs every file in that order."
          },
          "dry_run": {
            "type": "boolean",
            "description": "Default TRUE: report what would happen and write nothing. Send false to write."
          }
        }
      },
      "DatahunterImportBatches": {
        "type": "object",
        "properties": {
          "batches": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "steps": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "running",
                    "completed",
                    "failed"
                  ]
                },
                "summary": {},
                "error": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "started_at": {
                  "type": "string"
                },
                "finished_at": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "steps",
                "status",
                "error",
                "started_at",
                "finished_at"
              ]
            }
          },
          "staged": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "entity": {
                  "type": "string"
                },
                "state": {
                  "type": "string",
                  "enum": [
                    "imported",
                    "staged",
                    "promoted"
                  ]
                },
                "n": {
                  "type": "integer"
                }
              },
              "required": [
                "entity",
                "state",
                "n"
              ]
            }
          }
        },
        "required": [
          "batches",
          "staged"
        ]
      },
      "FloorPlan": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ]
          },
          "image_url": {
            "type": "string"
          },
          "image_width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "image_height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sort_order": {
            "type": "integer"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "units": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "unit_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "shape": {
                  "type": "array",
                  "items": {
                    "type": "array",
                    "prefixItems": [
                      {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      },
                      {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      }
                    ]
                  },
                  "minItems": 3,
                  "maxItems": 64,
                  "description": "Polygon of 3 to 64 `[x, y]` points, each coordinate a fraction (0..1) of the image width / height, origin top-left."
                }
              },
              "required": [
                "unit_id",
                "shape"
              ]
            },
            "description": "The unidades placed on the plan (live ones only)."
          }
        },
        "required": [
          "id",
          "tenant_id",
          "project_id",
          "name",
          "floor",
          "image_url",
          "image_width",
          "image_height",
          "sort_order",
          "created_by",
          "created_at",
          "updated_at",
          "units"
        ]
      },
      "CreateFloorPlanBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "«Piso 3», «Planta tipo»."
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 200,
            "description": "The floor this plan shows, when it shows one (negative = subterráneo)."
          },
          "image_url": {
            "type": "string",
            "maxLength": 2000,
            "format": "uri",
            "description": "Public URL of the plan image: upload it with `POST /tenant/media` first and send its `url`."
          },
          "image_width": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 20000,
            "description": "Intrinsic width in pixels (with image_height), optional."
          },
          "image_height": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 20000,
            "description": "Intrinsic height in pixels (with image_width), optional."
          },
          "sort_order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "Display order; lower first."
          }
        },
        "required": [
          "name",
          "image_url"
        ],
        "additionalProperties": false
      },
      "UpdateFloorPlanBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "«Piso 3», «Planta tipo»."
          },
          "floor": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": -10,
            "maximum": 200,
            "description": "The floor this plan shows, when it shows one (negative = subterráneo)."
          },
          "image_url": {
            "type": "string",
            "maxLength": 2000,
            "format": "uri",
            "description": "Public URL of the plan image: upload it with `POST /tenant/media` first and send its `url`."
          },
          "image_width": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 20000,
            "description": "Intrinsic width in pixels (with image_height), optional."
          },
          "image_height": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 20000,
            "description": "Intrinsic height in pixels (with image_width), optional."
          },
          "sort_order": {
            "type": "integer",
            "minimum": 0,
            "maximum": 10000,
            "description": "Display order; lower first."
          }
        },
        "additionalProperties": false
      },
      "SetFloorPlanUnitsBody": {
        "type": "object",
        "properties": {
          "units": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "unit_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "A unidad of the plan’s proyecto."
                },
                "shape": {
                  "type": "array",
                  "items": {
                    "type": "array",
                    "prefixItems": [
                      {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      },
                      {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      }
                    ]
                  },
                  "minItems": 3,
                  "maxItems": 64,
                  "description": "Polygon of 3 to 64 `[x, y]` points, each coordinate a fraction (0..1) of the image width / height, origin top-left."
                }
              },
              "required": [
                "unit_id",
                "shape"
              ]
            },
            "maxItems": 500,
            "description": "Every unidad on this plan, each once. Replaces what the plan had; an empty list clears it."
          }
        },
        "required": [
          "units"
        ],
        "additionalProperties": false
      },
      "PriceList": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The stable `price_list_version_id` a cotización records."
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "version_number": {
            "type": "integer",
            "description": "v1, v2, v3… per proyecto."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "published"
            ],
            "description": "draft: being built, editable. published: frozen; the newest published version is the proyecto’s current list."
          },
          "is_current": {
            "type": "boolean",
            "description": "Published and not superseded: the proyecto’s list today."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "based_on_version_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The version a draft was copied from."
          },
          "entry_count": {
            "type": "integer"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member’s user id, else the API key id."
          },
          "created_by_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "Credential family of the actor: ui_member | api_key | personal_token | connected_app."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "published_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member’s user id, else the API key id."
          },
          "published_by_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "Credential family of the actor: ui_member | api_key | personal_token | connected_app."
          },
          "superseded_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a newer version replaced it as the current list."
          },
          "superseded_by_version_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          }
        },
        "required": [
          "id",
          "project_id",
          "project",
          "version_number",
          "status",
          "is_current",
          "name",
          "notes",
          "based_on_version_id",
          "entry_count",
          "created_by",
          "created_by_kind",
          "created_at",
          "updated_at",
          "published_at",
          "published_by",
          "published_by_kind",
          "superseded_at",
          "superseded_by_version_id"
        ]
      },
      "CreatePriceListBody": {
        "type": "object",
        "properties": {
          "project_id": {
            "type": "string",
            "format": "uuid",
            "description": "The proyecto the list prices."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "description": "A name staff recognise («Lista noviembre 2026»)."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000,
            "description": "Free notes."
          },
          "copy_from": {
            "type": "string",
            "enum": [
              "current",
              "none"
            ],
            "description": "Default `current`: the draft starts with the current list’s prices (of unidades still in the stock). `none`: empty."
          }
        },
        "required": [
          "project_id"
        ]
      },
      "PriceImportColumns": {
        "type": "object",
        "properties": {
          "format": {
            "type": "string",
            "enum": [
              "csv",
              "xlsx",
              "xls"
            ]
          },
          "sheet_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "header": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "row_count": {
            "type": "integer"
          },
          "sample": {
            "type": "array",
            "items": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "suggested_mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "fields": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "enum": [
                    "unit_number",
                    "kind",
                    "price_uf"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "description": {
                  "type": "string"
                }
              },
              "required": [
                "key",
                "label",
                "required",
                "description"
              ]
            }
          }
        },
        "required": [
          "format",
          "sheet_name",
          "header",
          "row_count",
          "sample",
          "suggested_mapping",
          "fields"
        ]
      },
      "PriceListDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PriceList"
          },
          {
            "type": "object",
            "properties": {
              "compared_with_version_id": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uuid"
              },
              "summary": {
                "type": "object",
                "properties": {
                  "entries": {
                    "type": "integer"
                  },
                  "added": {
                    "type": "integer"
                  },
                  "changed": {
                    "type": "integer"
                  },
                  "unchanged": {
                    "type": "integer"
                  },
                  "units_without_price": {
                    "type": "integer",
                    "description": "Live unidades of the proyecto with no price in this version (publishing leaves their price as it is)."
                  },
                  "removed_units": {
                    "type": "integer"
                  }
                },
                "required": [
                  "entries",
                  "added",
                  "changed",
                  "unchanged",
                  "units_without_price",
                  "removed_units"
                ]
              },
              "entries": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "unit_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "display_id": {
                      "type": "string"
                    },
                    "kind": {
                      "type": "string",
                      "description": "The unidad’s kind when priced."
                    },
                    "unit_number": {
                      "type": "string",
                      "description": "The unidad’s number when priced."
                    },
                    "title": {
                      "type": "string"
                    },
                    "floor": {
                      "type": [
                        "integer",
                        "null"
                      ]
                    },
                    "unit_model": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "name"
                      ]
                    },
                    "unit_status": {
                      "type": "string",
                      "description": "The unidad’s status now."
                    },
                    "unit_removed": {
                      "type": "boolean",
                      "description": "The unidad left the stock after it was priced; publishing skips it."
                    },
                    "price_uf": {
                      "type": "number"
                    },
                    "price_clp": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "price_uf × the day’s UF (`meta.uf_value`); null when no UF is known."
                    },
                    "previous_price_uf": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "The price in `compared_with_version_id` (a draft: the current list; a published version: the one it replaced)."
                    },
                    "source": {
                      "type": "string",
                      "enum": [
                        "copy",
                        "import",
                        "manual"
                      ]
                    },
                    "set_by": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The member’s user id, else the API key id."
                    },
                    "set_by_kind": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Credential family of the actor: ui_member | api_key | personal_token | connected_app."
                    },
                    "set_at": {
                      "type": "string",
                      "description": "When this price was set."
                    }
                  },
                  "required": [
                    "unit_id",
                    "display_id",
                    "kind",
                    "unit_number",
                    "title",
                    "floor",
                    "unit_model",
                    "unit_status",
                    "unit_removed",
                    "price_uf",
                    "price_clp",
                    "previous_price_uf",
                    "source",
                    "set_by",
                    "set_by_kind",
                    "set_at"
                  ]
                }
              }
            },
            "required": [
              "compared_with_version_id",
              "summary",
              "entries"
            ]
          }
        ]
      },
      "UpdatePriceListBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 5000
          }
        }
      },
      "SetPriceListEntriesBody": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "unit_id": {
                  "type": "string",
                  "format": "uuid",
                  "description": "A unidad of the list’s proyecto."
                },
                "price_uf": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "exclusiveMinimum": 0,
                  "maximum": 1000000,
                  "description": "The price in UF; null takes the unidad out of the draft."
                }
              },
              "required": [
                "unit_id",
                "price_uf"
              ]
            },
            "minItems": 1,
            "maxItems": 5000
          }
        },
        "required": [
          "entries"
        ]
      },
      "PriceImportReport": {
        "type": "object",
        "properties": {
          "dry_run": {
            "type": "boolean"
          },
          "price_list_version_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total_rows": {
                "type": "integer"
              },
              "add": {
                "type": "integer"
              },
              "change": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "error": {
                "type": "integer"
              },
              "not_in_file": {
                "type": "integer",
                "description": "Live unidades the file does not mention: they keep the draft’s price."
              }
            },
            "required": [
              "total_rows",
              "add",
              "change",
              "unchanged",
              "error",
              "not_in_file"
            ]
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "row_number": {
                  "type": "integer"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "add",
                    "change",
                    "unchanged",
                    "error"
                  ]
                },
                "unit_number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "kind": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "departamento",
                    "casa",
                    "estacionamiento",
                    "bodega"
                  ],
                  "description": "departamento | casa | estacionamiento | bodega"
                },
                "unit_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "display_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "price_uf": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "draft_price_uf": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "current_price_uf": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "errors": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "field": {
                        "type": "string"
                      },
                      "message": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "field",
                      "message"
                    ]
                  }
                }
              },
              "required": [
                "row_number",
                "action",
                "unit_number",
                "kind",
                "unit_id",
                "display_id",
                "price_uf",
                "draft_price_uf",
                "current_price_uf",
                "errors"
              ]
            }
          }
        },
        "required": [
          "dry_run",
          "price_list_version_id",
          "project_id",
          "mapping",
          "summary",
          "rows"
        ]
      },
      "ImportPricesBody": {
        "type": "object",
        "properties": {
          "file": {
            "description": "Multipart only: the .xlsx, .xls or .csv."
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": {
                "anyOf": [
                  {
                    "type": "string"
                  },
                  {
                    "type": "number"
                  },
                  {
                    "type": "boolean"
                  },
                  {
                    "type": "null"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "maxItems": 5000,
            "description": "JSON only, instead of a file: one object per unidad keyed by column name (`{ \"Número\": \"1203\", \"Tipo\": \"Depto\", \"Precio UF\": \"3.250,5\" }`)."
          },
          "mapping": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ],
              "maxLength": 200
            },
            "description": "Field (unit_number, kind, price_uf) → column name. Omitted: the suggested mapping."
          },
          "default_kind": {
            "type": "string",
            "enum": [
              "departamento",
              "casa",
              "estacionamiento",
              "bodega"
            ],
            "description": "The kind of a row with no kind (default departamento)."
          },
          "dry_run": {
            "type": "boolean",
            "description": "Default TRUE: report and write nothing. false writes."
          }
        }
      },
      "PriceListPublishReport": {
        "type": "object",
        "properties": {
          "price_list": {
            "$ref": "#/components/schemas/PriceList"
          },
          "superseded_version_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The version that was current until now (still readable)."
          },
          "units_priced": {
            "type": "integer"
          },
          "units_changed": {
            "type": "integer"
          },
          "skipped_removed": {
            "type": "integer"
          },
          "units_without_price": {
            "type": "integer"
          }
        },
        "required": [
          "price_list",
          "superseded_version_id",
          "units_priced",
          "units_changed",
          "skipped_removed",
          "units_without_price"
        ]
      },
      "UnitPriceHistoryEntry": {
        "type": "object",
        "properties": {
          "price_list_version_id": {
            "type": "string",
            "format": "uuid"
          },
          "version_number": {
            "type": "integer"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_current": {
            "type": "boolean"
          },
          "published_at": {
            "type": "string"
          },
          "published_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member’s user id, else the API key id."
          },
          "published_by_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "Credential family of the actor: ui_member | api_key | personal_token | connected_app."
          },
          "superseded_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_uf": {
            "type": "number"
          },
          "price_clp": {
            "type": [
              "integer",
              "null"
            ]
          },
          "set_by": {
            "type": [
              "string",
              "null"
            ],
            "description": "The member’s user id, else the API key id."
          },
          "set_by_kind": {
            "type": [
              "string",
              "null"
            ],
            "description": "Credential family of the actor: ui_member | api_key | personal_token | connected_app."
          },
          "set_at": {
            "type": "string"
          }
        },
        "required": [
          "price_list_version_id",
          "version_number",
          "name",
          "is_current",
          "published_at",
          "published_by",
          "published_by_kind",
          "superseded_at",
          "price_uf",
          "price_clp",
          "set_by",
          "set_by_kind",
          "set_at"
        ]
      },
      "DeveloperReport": {
        "type": "object",
        "properties": {
          "pipeline_id": {
            "type": "string"
          },
          "period": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string"
              },
              "to": {
                "type": "string"
              },
              "timezone": {
                "type": "string"
              }
            },
            "required": [
              "from",
              "to",
              "timezone"
            ]
          },
          "filters": {
            "type": "object",
            "properties": {
              "project_id": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sales_office_id": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "project_id",
              "sales_office_id"
            ]
          },
          "totals": {
            "type": "object",
            "properties": {
              "funnel": {
                "type": "object",
                "properties": {
                  "cohort": {
                    "type": "integer",
                    "description": "Leads that arrived in the period."
                  },
                  "steps": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "reached": {
                          "type": "integer",
                          "description": "Cohort leads that reached the milestone or a later one."
                        },
                        "conversion_from_previous": {
                          "type": [
                            "number",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "key",
                        "name",
                        "reached",
                        "conversion_from_previous"
                      ]
                    }
                  },
                  "lost": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "Cohort leads standing on the desistimiento stage today."
                  }
                },
                "required": [
                  "cohort",
                  "steps",
                  "lost"
                ]
              },
              "stock": {
                "type": "object",
                "properties": {
                  "by_status": {
                    "type": "object",
                    "properties": {
                      "disponible": {
                        "type": "integer"
                      },
                      "bloqueada": {
                        "type": "integer"
                      },
                      "reservada": {
                        "type": "integer"
                      },
                      "promesada": {
                        "type": "integer"
                      },
                      "vendida": {
                        "type": "integer"
                      },
                      "pausada": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "disponible",
                      "bloqueada",
                      "reservada",
                      "promesada",
                      "vendida",
                      "pausada"
                    ],
                    "description": "Departamentos and casas by status."
                  },
                  "dwellings": {
                    "type": "integer"
                  },
                  "extras_by_status": {
                    "type": "object",
                    "properties": {
                      "disponible": {
                        "type": "integer"
                      },
                      "bloqueada": {
                        "type": "integer"
                      },
                      "reservada": {
                        "type": "integer"
                      },
                      "promesada": {
                        "type": "integer"
                      },
                      "vendida": {
                        "type": "integer"
                      },
                      "pausada": {
                        "type": "integer"
                      }
                    },
                    "required": [
                      "disponible",
                      "bloqueada",
                      "reservada",
                      "promesada",
                      "vendida",
                      "pausada"
                    ],
                    "description": "Estacionamientos and bodegas sold on their own, by status."
                  },
                  "extras": {
                    "type": "integer"
                  },
                  "available_value_uf": {
                    "type": "number",
                    "description": "Σ list price in UF of the disponible departamentos and casas."
                  }
                },
                "required": [
                  "by_status",
                  "dwellings",
                  "extras_by_status",
                  "extras",
                  "available_value_uf"
                ]
              },
              "absorption": {
                "type": "object",
                "properties": {
                  "on_sale": {
                    "type": "integer",
                    "description": "Departamentos and casas not pausada."
                  },
                  "committed": {
                    "type": "integer",
                    "description": "reservada + promesada + vendida."
                  },
                  "absorption_rate": {
                    "type": "number",
                    "description": "committed / on_sale, 0..1."
                  },
                  "sold_rate": {
                    "type": "number",
                    "description": "vendida / on_sale, 0..1."
                  },
                  "reservations": {
                    "type": "integer",
                    "description": "Reservas that landed in the period."
                  },
                  "reservations_per_month": {
                    "type": "number"
                  },
                  "months_to_sell_out": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "description": "Disponible unidades / reservations_per_month; null while no reserva landed."
                  },
                  "by_month": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "reservations": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "month",
                        "reservations"
                      ]
                    }
                  }
                },
                "required": [
                  "on_sale",
                  "committed",
                  "absorption_rate",
                  "sold_rate",
                  "reservations",
                  "reservations_per_month",
                  "months_to_sell_out",
                  "by_month"
                ]
              },
              "desistimientos": {
                "type": "object",
                "properties": {
                  "total": {
                    "type": "integer"
                  },
                  "by_reason": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "reason": {
                          "type": "string",
                          "description": "bought_elsewhere | financing_rejected | unit_unavailable | price | not_ready | unresponsive | not_interested | other | unclassified"
                        },
                        "count": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "reason",
                        "count"
                      ]
                    }
                  }
                },
                "required": [
                  "total",
                  "by_reason"
                ]
              }
            },
            "required": [
              "funnel",
              "stock",
              "absorption",
              "desistimientos"
            ],
            "description": "Every proyecto of the filter, added up."
          },
          "projects": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "project_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "null = leads that name no proyecto (unrestricted callers only)."
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "sales_office_location_id": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "funnel": {
                  "type": "object",
                  "properties": {
                    "cohort": {
                      "type": "integer",
                      "description": "Leads that arrived in the period."
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "reached": {
                            "type": "integer",
                            "description": "Cohort leads that reached the milestone or a later one."
                          },
                          "conversion_from_previous": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "reached",
                          "conversion_from_previous"
                        ]
                      }
                    },
                    "lost": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Cohort leads standing on the desistimiento stage today."
                    }
                  },
                  "required": [
                    "cohort",
                    "steps",
                    "lost"
                  ]
                },
                "stock": {
                  "type": "object",
                  "properties": {
                    "by_status": {
                      "type": "object",
                      "properties": {
                        "disponible": {
                          "type": "integer"
                        },
                        "bloqueada": {
                          "type": "integer"
                        },
                        "reservada": {
                          "type": "integer"
                        },
                        "promesada": {
                          "type": "integer"
                        },
                        "vendida": {
                          "type": "integer"
                        },
                        "pausada": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "disponible",
                        "bloqueada",
                        "reservada",
                        "promesada",
                        "vendida",
                        "pausada"
                      ],
                      "description": "Departamentos and casas by status."
                    },
                    "dwellings": {
                      "type": "integer"
                    },
                    "extras_by_status": {
                      "type": "object",
                      "properties": {
                        "disponible": {
                          "type": "integer"
                        },
                        "bloqueada": {
                          "type": "integer"
                        },
                        "reservada": {
                          "type": "integer"
                        },
                        "promesada": {
                          "type": "integer"
                        },
                        "vendida": {
                          "type": "integer"
                        },
                        "pausada": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "disponible",
                        "bloqueada",
                        "reservada",
                        "promesada",
                        "vendida",
                        "pausada"
                      ],
                      "description": "Estacionamientos and bodegas sold on their own, by status."
                    },
                    "extras": {
                      "type": "integer"
                    },
                    "available_value_uf": {
                      "type": "number",
                      "description": "Σ list price in UF of the disponible departamentos and casas."
                    }
                  },
                  "required": [
                    "by_status",
                    "dwellings",
                    "extras_by_status",
                    "extras",
                    "available_value_uf"
                  ]
                },
                "absorption": {
                  "type": "object",
                  "properties": {
                    "on_sale": {
                      "type": "integer",
                      "description": "Departamentos and casas not pausada."
                    },
                    "committed": {
                      "type": "integer",
                      "description": "reservada + promesada + vendida."
                    },
                    "absorption_rate": {
                      "type": "number",
                      "description": "committed / on_sale, 0..1."
                    },
                    "sold_rate": {
                      "type": "number",
                      "description": "vendida / on_sale, 0..1."
                    },
                    "reservations": {
                      "type": "integer",
                      "description": "Reservas that landed in the period."
                    },
                    "reservations_per_month": {
                      "type": "number"
                    },
                    "months_to_sell_out": {
                      "type": [
                        "number",
                        "null"
                      ],
                      "description": "Disponible unidades / reservations_per_month; null while no reserva landed."
                    },
                    "by_month": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "month": {
                            "type": "string"
                          },
                          "reservations": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "month",
                          "reservations"
                        ]
                      }
                    }
                  },
                  "required": [
                    "on_sale",
                    "committed",
                    "absorption_rate",
                    "sold_rate",
                    "reservations",
                    "reservations_per_month",
                    "months_to_sell_out",
                    "by_month"
                  ]
                },
                "desistimientos": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer"
                    },
                    "by_reason": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "reason": {
                            "type": "string",
                            "description": "bought_elsewhere | financing_rejected | unit_unavailable | price | not_ready | unresponsive | not_interested | other | unclassified"
                          },
                          "count": {
                            "type": "integer"
                          }
                        },
                        "required": [
                          "reason",
                          "count"
                        ]
                      }
                    }
                  },
                  "required": [
                    "total",
                    "by_reason"
                  ]
                }
              },
              "required": [
                "project_id",
                "name",
                "sales_office_location_id",
                "funnel",
                "stock",
                "absorption",
                "desistimientos"
              ]
            }
          },
          "ejecutivos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "rank": {
                  "type": "integer"
                },
                "user_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "null = leads nobody owns."
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "funnel": {
                  "type": "object",
                  "properties": {
                    "cohort": {
                      "type": "integer",
                      "description": "Leads that arrived in the period."
                    },
                    "steps": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "key": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "reached": {
                            "type": "integer",
                            "description": "Cohort leads that reached the milestone or a later one."
                          },
                          "conversion_from_previous": {
                            "type": [
                              "number",
                              "null"
                            ]
                          }
                        },
                        "required": [
                          "key",
                          "name",
                          "reached",
                          "conversion_from_previous"
                        ]
                      }
                    },
                    "lost": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "description": "Cohort leads standing on the desistimiento stage today."
                    }
                  },
                  "required": [
                    "cohort",
                    "steps",
                    "lost"
                  ]
                }
              },
              "required": [
                "rank",
                "user_id",
                "email",
                "funnel"
              ]
            }
          }
        },
        "required": [
          "pipeline_id",
          "period",
          "filters",
          "totals",
          "projects",
          "ejecutivos"
        ]
      },
      "ReservaTerms": {
        "type": "object",
        "properties": {
          "amount": {
            "type": [
              "number",
              "null"
            ],
            "exclusiveMinimum": 0,
            "maximum": 1000000000,
            "description": "What a reserva costs, in `currency`. Null = no default."
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ],
            "description": "UF or CLP, together with `amount`."
          },
          "hold_hours": {
            "type": "integer",
            "minimum": 1,
            "maximum": 720,
            "description": "How long a reserva keeps its unidades bloqueada (default 48)."
          }
        },
        "required": [
          "amount",
          "currency",
          "hold_hours"
        ]
      },
      "Reserva": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "cotizacion_id": {
            "type": "string",
            "format": "uuid"
          },
          "cotizacion_display_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "co_buyer_contact_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "owner_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "property_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "The unidades the reserva holds."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "paid",
              "expired",
              "cancelled"
            ],
            "description": "pending: the unidades are bloqueada until `expires_at` while the buyer pays. paid: the negocio is open (`negocio_id`). expired: the deadline passed unpaid and the unidades are disponible again. cancelled: a person released them."
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "amount_clp": {
            "type": "integer",
            "description": "What the buyer is charged, CLP."
          },
          "uf_value_clp": {
            "type": [
              "number",
              "null"
            ]
          },
          "expires_at": {
            "type": "string",
            "description": "When the hold ends (ISO instant)."
          },
          "obligation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "negocio_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by_kind": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "expired_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_clp": {
            "type": "integer"
          },
          "outstanding_clp": {
            "type": "integer"
          },
          "payment_state": {
            "type": [
              "string",
              "null"
            ],
            "description": "pending | partial | paid | overpaid | overdue | cancelled …"
          },
          "is_overdue": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "project_id",
          "project_name",
          "cotizacion_id",
          "cotizacion_display_id",
          "lead_id",
          "contact_id",
          "co_buyer_contact_ids",
          "owner_user_id",
          "conversation_id",
          "property_ids",
          "status",
          "amount",
          "currency",
          "amount_clp",
          "uf_value_clp",
          "expires_at",
          "obligation_id",
          "negocio_id",
          "created_by",
          "created_by_kind",
          "created_at",
          "updated_at",
          "paid_at",
          "expired_at",
          "cancelled_at",
          "cancelled_reason",
          "paid_clp",
          "outstanding_clp",
          "payment_state",
          "is_overdue"
        ]
      },
      "ReservaPayment": {
        "type": "object",
        "properties": {
          "mode": {
            "type": "string",
            "description": "The workspace’s charging preference for a reserva."
          },
          "link": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "checkout_id": {
                "type": "string",
                "format": "uuid"
              },
              "checkout_url": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              }
            },
            "required": [
              "checkout_id",
              "checkout_url",
              "amount_clp"
            ],
            "description": "The payment link, when the preference offers one."
          },
          "transfer_accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "alias": {
                  "type": "string"
                },
                "bank": {
                  "type": "string"
                },
                "account_type": {
                  "type": "string"
                },
                "account_number": {
                  "type": "string"
                },
                "holder_name": {
                  "type": "string"
                },
                "holder_rut": {
                  "type": "string"
                }
              },
              "required": [
                "alias",
                "bank",
                "account_type",
                "account_number",
                "holder_name",
                "holder_rut"
              ]
            },
            "description": "Bank accounts for a transfer, when the preference offers it."
          }
        },
        "required": [
          "mode",
          "link",
          "transfer_accounts"
        ]
      },
      "NegocioSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string",
            "description": "NG-n, a label (routes take the uuid)."
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "cotizacion_id": {
            "type": "string",
            "format": "uuid"
          },
          "reserva_id": {
            "type": "string",
            "format": "uuid"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "buyer_name": {
            "type": "string"
          },
          "owner_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "stage": {
            "type": "string",
            "enum": [
              "reservado",
              "promesado",
              "escriturado",
              "entregado",
              "desistido"
            ],
            "description": "Where the sale stands: reservado (the reserva is paid) → promesado → escriturado → entregado, or desistido."
          },
          "currency": {
            "type": "string",
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "total": {
            "type": "number"
          },
          "uf_value_clp": {
            "type": [
              "number",
              "null"
            ]
          },
          "reserved_at": {
            "type": "string"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "promesa_due_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "When the promesa should be signed."
          },
          "promesa_signed_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "escritura_due_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "escritura_signed_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "escritura_notary": {
            "type": [
              "string",
              "null"
            ]
          },
          "entrega_due_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "entregado_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "desisted_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "desist_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "desist_money_outcome": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "returned",
              "retained",
              "applied"
            ],
            "description": "What happens to the money the buyer paid: returned (devuelto), retained (retenido) or applied (aplicado to another negocio)."
          },
          "desist_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "milestone_alerted_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "The overdue deadline already alerted (<milestone>:<day>)."
          },
          "next_deadline": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "milestone": {
                "type": "string",
                "enum": [
                  "promesa",
                  "escritura",
                  "entrega"
                ]
              },
              "due_on": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                "description": "YYYY-MM-DD"
              },
              "overdue": {
                "type": "boolean"
              }
            },
            "required": [
              "milestone",
              "due_on",
              "overdue"
            ],
            "description": "The deadline the negocio is working towards now."
          }
        },
        "required": [
          "id",
          "display_id",
          "project_id",
          "project_name",
          "cotizacion_id",
          "reserva_id",
          "lead_id",
          "contact_id",
          "buyer_name",
          "owner_user_id",
          "conversation_id",
          "stage",
          "currency",
          "total",
          "uf_value_clp",
          "reserved_at",
          "created_at",
          "updated_at",
          "promesa_due_on",
          "promesa_signed_on",
          "escritura_due_on",
          "escritura_signed_on",
          "escritura_notary",
          "entrega_due_on",
          "entregado_on",
          "desisted_on",
          "desist_reason",
          "desist_money_outcome",
          "desist_notes",
          "milestone_alerted_key",
          "next_deadline"
        ]
      },
      "Negocio": {
        "allOf": [
          {
            "$ref": "#/components/schemas/NegocioSummary"
          },
          {
            "type": "object",
            "properties": {
              "buyers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "contact_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "contact_name": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "enum": [
                        "titular",
                        "cotitular"
                      ]
                    },
                    "position": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "id",
                    "contact_id",
                    "contact_name",
                    "role",
                    "position"
                  ]
                }
              },
              "units": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "property_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "property_display_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "kind": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "label": {
                      "type": "string"
                    },
                    "unit_number": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "price": {
                      "type": "number",
                      "description": "As the accepted cotización priced it."
                    },
                    "position": {
                      "type": "integer"
                    },
                    "released_at": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "status": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "The unidad’s current status."
                    }
                  },
                  "required": [
                    "id",
                    "property_id",
                    "property_display_id",
                    "kind",
                    "label",
                    "unit_number",
                    "price",
                    "position",
                    "released_at",
                    "status"
                  ]
                }
              },
              "reserva": {
                "type": [
                  "object",
                  "null"
                ],
                "properties": {
                  "id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "project_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "project_name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "cotizacion_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "cotizacion_display_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "co_buyer_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "property_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "description": "The unidades the reserva holds."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "paid",
                      "expired",
                      "cancelled"
                    ],
                    "description": "pending: the unidades are bloqueada until `expires_at` while the buyer pays. paid: the negocio is open (`negocio_id`). expired: the deadline passed unpaid and the unidades are disponible again. cancelled: a person released them."
                  },
                  "amount": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "UF",
                      "CLP"
                    ]
                  },
                  "amount_clp": {
                    "type": "integer",
                    "description": "What the buyer is charged, CLP."
                  },
                  "uf_value_clp": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "expires_at": {
                    "type": "string",
                    "description": "When the hold ends (ISO instant)."
                  },
                  "obligation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "negocio_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "created_by": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "created_by_kind": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "created_at": {
                    "type": "string"
                  },
                  "updated_at": {
                    "type": "string"
                  },
                  "paid_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "expired_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "cancelled_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "cancelled_reason": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "id",
                  "project_id",
                  "project_name",
                  "cotizacion_id",
                  "cotizacion_display_id",
                  "lead_id",
                  "contact_id",
                  "co_buyer_contact_ids",
                  "owner_user_id",
                  "conversation_id",
                  "property_ids",
                  "status",
                  "amount",
                  "currency",
                  "amount_clp",
                  "uf_value_clp",
                  "expires_at",
                  "obligation_id",
                  "negocio_id",
                  "created_by",
                  "created_by_kind",
                  "created_at",
                  "updated_at",
                  "paid_at",
                  "expired_at",
                  "cancelled_at",
                  "cancelled_reason"
                ]
              }
            },
            "required": [
              "buyers",
              "units",
              "reserva"
            ]
          }
        ]
      },
      "NegocioMilestoneTerms": {
        "type": "object",
        "properties": {
          "promesa_due_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 730,
            "description": "Days after the reserva to sign the promesa (default 30)."
          },
          "escritura_due_days": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 1825,
            "description": "Days after the promesa to sign the escritura (null = none)."
          },
          "entrega_due_days": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 1825,
            "description": "Days after the escritura to deliver (null = none)."
          }
        },
        "required": [
          "promesa_due_days",
          "escritura_due_days",
          "entrega_due_days"
        ]
      },
      "NegocioDocumentRequirement": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "pattern": "^[a-z0-9_]{1,60}$",
            "description": "Stable key (snake_case), e.g. `liquidaciones`."
          },
          "label": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "What the buyer reads."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500
          },
          "required": {
            "type": "boolean"
          }
        },
        "required": [
          "key",
          "label",
          "required"
        ]
      },
      "NegocioDocumentRequirements": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NegocioDocumentRequirement"
            }
          },
          "is_default": {
            "type": "boolean",
            "description": "The proyecto states none: the platform default list."
          },
          "reminder_days": {
            "type": "integer",
            "description": "Days between buyer reminders (0 = none)."
          }
        },
        "required": [
          "items",
          "is_default",
          "reminder_days"
        ]
      },
      "NegocioDocument": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "negocio_id": {
            "type": "string",
            "format": "uuid"
          },
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "position": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "missing",
              "received",
              "approved",
              "rejected"
            ],
            "description": "missing → received (buyer in chat, staff upload, a signed document) → approved | rejected (back to the buyer)."
          },
          "file": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "filename": {
                "type": "string"
              },
              "mime": {
                "type": "string"
              },
              "size": {
                "type": "integer"
              },
              "uploaded_at": {
                "type": "string"
              }
            },
            "required": [
              "filename",
              "mime",
              "size",
              "uploaded_at"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "chat",
              "staff",
              "signing"
            ]
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "received_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "reviewed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "review_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "reminder_count": {
            "type": "integer"
          },
          "last_reminded_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "negocio_id",
          "key",
          "label",
          "description",
          "required",
          "position",
          "status",
          "file",
          "source",
          "message_id",
          "received_at",
          "reviewed_at",
          "reviewed_by",
          "review_note",
          "reminder_count",
          "last_reminded_at",
          "created_at",
          "updated_at"
        ]
      },
      "NegocioDocumentChecklist": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NegocioDocument"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "required": {
                "type": "integer"
              },
              "received": {
                "type": "integer"
              },
              "approved": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              }
            },
            "required": [
              "required",
              "received",
              "approved",
              "pending"
            ]
          }
        },
        "required": [
          "items",
          "summary"
        ]
      },
      "NegocioBankApproval": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "negocio_id": {
            "type": "string",
            "format": "uuid"
          },
          "bank": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "en_evaluacion",
              "pre_aprobado",
              "aprobado",
              "rechazado"
            ],
            "description": "Where this bank’s crédito hipotecario stands."
          },
          "amount": {
            "type": [
              "number",
              "null"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "rate_annual": {
            "type": [
              "number",
              "null"
            ]
          },
          "term_years": {
            "type": [
              "integer",
              "null"
            ]
          },
          "decided_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "expires_on": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "negocio_id",
          "bank",
          "status",
          "amount",
          "currency",
          "rate_annual",
          "term_years",
          "decided_on",
          "expires_on",
          "notes",
          "created_by",
          "created_at",
          "updated_at"
        ]
      },
      "NegocioSigningTemplate": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "orden_reserva"
            ],
            "description": "The document: the orden de reserva."
          },
          "title": {
            "type": "string"
          },
          "body": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "placeholders": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "kind",
          "title",
          "body",
          "is_default",
          "placeholders"
        ]
      },
      "NegocioSignature": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "negocio_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "orden_reserva"
            ],
            "description": "The document: the orden de reserva."
          },
          "provider": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending_identity",
              "pending_signature",
              "signed",
              "declined",
              "cancelled",
              "expired"
            ],
            "description": "pending_identity: the buyer has the link (code + cédula/selfie pending). pending_signature: identity done, signing at the provider. signed: the signed PDF and certificate are on the checklist."
          },
          "title": {
            "type": "string"
          },
          "signer_name": {
            "type": "string"
          },
          "signer_phone_hint": {
            "type": [
              "string",
              "null"
            ]
          },
          "document_sha256": {
            "type": "string",
            "description": "SHA-256 of the PDF the buyer signs."
          },
          "otp_channel": {
            "type": [
              "string",
              "null"
            ]
          },
          "otp_verified_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "identity_submitted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": "string"
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "files": {
            "type": "object",
            "properties": {
              "unsigned": {
                "type": "boolean"
              },
              "signed": {
                "type": "boolean"
              },
              "certificate": {
                "type": "boolean"
              },
              "id_front": {
                "type": "boolean"
              },
              "selfie": {
                "type": "boolean"
              }
            },
            "required": [
              "unsigned",
              "signed",
              "certificate",
              "id_front",
              "selfie"
            ]
          },
          "evidence": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "at": {
                  "type": "string"
                },
                "event": {
                  "type": "string"
                },
                "ip": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "user_agent": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "detail": {
                  "type": "object",
                  "additionalProperties": {}
                }
              },
              "required": [
                "at",
                "event"
              ]
            },
            "description": "The append-only trail."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "negocio_id",
          "kind",
          "provider",
          "status",
          "title",
          "signer_name",
          "signer_phone_hint",
          "document_sha256",
          "otp_channel",
          "otp_verified_at",
          "identity_submitted_at",
          "signed_at",
          "expires_at",
          "cancelled_at",
          "files",
          "evidence",
          "created_by",
          "created_at"
        ]
      },
      "NegocioPieTerms": {
        "type": "object",
        "properties": {
          "reminder_days_before": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "description": "Days before a cuota is due that the buyer is reminded (0 = never). Default 3."
          },
          "reminder_days_after": {
            "type": "integer",
            "minimum": 0,
            "maximum": 30,
            "description": "Days after an unpaid due day that the buyer is reminded and the ejecutivo told (0 = never). Default 2."
          }
        },
        "required": [
          "reminder_days_before",
          "reminder_days_after"
        ]
      },
      "NegocioCuota": {
        "type": "object",
        "properties": {
          "number": {
            "type": "integer"
          },
          "due_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string",
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "amount_clp": {
            "type": "integer",
            "description": "What is charged now (UF at today’s UF)."
          },
          "obligation_id": {
            "type": "string",
            "format": "uuid"
          },
          "paid_clp": {
            "type": "integer"
          },
          "outstanding_clp": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "description": "pending | partial | paid | overpaid | overdue | overdue_partial | cancelled"
          },
          "is_overdue": {
            "type": "boolean"
          }
        },
        "required": [
          "number",
          "due_on",
          "amount",
          "currency",
          "amount_clp",
          "obligation_id",
          "paid_clp",
          "outstanding_clp",
          "state",
          "is_overdue"
        ]
      },
      "NegocioStatement": {
        "type": "object",
        "properties": {
          "negocio_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "buyer_name": {
            "type": "string"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "type": "string",
            "enum": [
              "UF",
              "CLP"
            ]
          },
          "total": {
            "type": "number"
          },
          "reserva": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "UF",
                  "CLP"
                ]
              },
              "amount_clp": {
                "type": "integer"
              },
              "paid_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "amount",
              "currency",
              "amount_clp",
              "paid_at"
            ]
          },
          "cuotas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NegocioCuota"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "pie_clp": {
                "type": "integer"
              },
              "paid_clp": {
                "type": "integer"
              },
              "outstanding_clp": {
                "type": "integer"
              },
              "overdue_clp": {
                "type": "integer"
              },
              "cuotas_paid": {
                "type": "integer"
              },
              "cuotas_overdue": {
                "type": "integer"
              }
            },
            "required": [
              "pie_clp",
              "paid_clp",
              "outstanding_clp",
              "overdue_clp",
              "cuotas_paid",
              "cuotas_overdue"
            ]
          },
          "next_due": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NegocioCuota"
              },
              {
                "type": [
                  "object",
                  "null"
                ]
              }
            ]
          },
          "as_of": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          }
        },
        "required": [
          "negocio_id",
          "display_id",
          "buyer_name",
          "project_name",
          "currency",
          "total",
          "reserva",
          "cuotas",
          "totals",
          "next_due",
          "as_of"
        ]
      },
      "NegocioCuotaPayment": {
        "type": "object",
        "properties": {
          "number": {
            "type": "integer"
          },
          "due_on": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "YYYY-MM-DD"
          },
          "amount_clp": {
            "type": "integer"
          },
          "mode": {
            "type": "string"
          },
          "link": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "checkout_id": {
                "type": "string",
                "format": "uuid"
              },
              "checkout_url": {
                "type": "string"
              },
              "amount_clp": {
                "type": "integer"
              }
            },
            "required": [
              "checkout_id",
              "checkout_url",
              "amount_clp"
            ]
          },
          "transfer_accounts": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "alias": {
                  "type": "string"
                },
                "bank": {
                  "type": "string"
                },
                "account_type": {
                  "type": "string"
                },
                "account_number": {
                  "type": "string"
                },
                "holder_name": {
                  "type": "string"
                },
                "holder_rut": {
                  "type": "string"
                }
              },
              "required": [
                "alias",
                "bank",
                "account_type",
                "account_number",
                "holder_name",
                "holder_rut"
              ]
            }
          }
        },
        "required": [
          "number",
          "due_on",
          "amount_clp",
          "mode",
          "link",
          "transfer_accounts"
        ]
      },
      "NegocioCobranzaRow": {
        "type": "object",
        "properties": {
          "negocio_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "buyer_name": {
            "type": "string"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner_user_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "days_overdue": {
            "type": "integer"
          },
          "cuota": {
            "$ref": "#/components/schemas/NegocioCuota"
          }
        },
        "required": [
          "negocio_id",
          "display_id",
          "buyer_name",
          "project_id",
          "project_name",
          "owner_user_id",
          "contact_id",
          "days_overdue",
          "cuota"
        ]
      },
      "MarketingKitContent": {
        "type": "object",
        "properties": {
          "listing": {
            "type": "object",
            "properties": {
              "headline": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "summary": {
                "type": "string",
                "minLength": 1,
                "maxLength": 300
              },
              "body": {
                "type": "string",
                "minLength": 1,
                "maxLength": 5000
              },
              "highlights": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "maxItems": 12
              }
            },
            "required": [
              "headline",
              "summary",
              "body",
              "highlights"
            ],
            "description": "Copy for a portal listing or the proyecto’s page."
          },
          "instagram": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "caption": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2200,
                  "description": "The post’s text, hashtags apart."
                },
                "hashtags": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[\\p{L}\\p{N}_]{1,60}$"
                  },
                  "maxItems": 30
                },
                "image_url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 2000,
                  "format": "uri",
                  "description": "A public image of the proyecto or a tipología; null = pick one."
                }
              },
              "required": [
                "caption",
                "hashtags",
                "image_url"
              ]
            },
            "maxItems": 10
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500,
                "description": "ONE paragraph. It fills the {{mensaje}} variable of the approved novedad_proyecto template, so it cannot hold line breaks."
              }
            },
            "required": [
              "message"
            ],
            "description": "The WhatsApp broadcast message."
          }
        },
        "required": [
          "listing",
          "instagram",
          "whatsapp"
        ]
      },
      "MarketingKit": {
        "type": "object",
        "properties": {
          "kit": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "project_id": {
                "type": "string",
                "format": "uuid"
              },
              "content": {
                "$ref": "#/components/schemas/MarketingKitContent"
              },
              "generated_at": {
                "type": "string"
              },
              "generated_by": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "edited_at": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Null while the kit is as generated."
              },
              "edited_by": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "created_at": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "project_id",
              "content",
              "generated_at",
              "generated_by",
              "edited_at",
              "edited_by",
              "created_at",
              "updated_at"
            ],
            "description": "Null before the first generation."
          },
          "source": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comuna": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "stage": {
                "type": "string",
                "enum": [
                  "en_blanco",
                  "en_verde",
                  "entrega_inmediata"
                ]
              },
              "delivery_date": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "subsidies": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "photos": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sales_office_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "tipologias": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string"
                    },
                    "bedrooms": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "bathrooms": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "usable_area_m2": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "total_area_m2": {
                      "type": [
                        "number",
                        "null"
                      ]
                    },
                    "cover": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "available": {
                      "type": "integer"
                    },
                    "from_price_uf": {
                      "type": [
                        "number",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "name",
                    "bedrooms",
                    "bathrooms",
                    "usable_area_m2",
                    "total_area_m2",
                    "cover",
                    "available",
                    "from_price_uf"
                  ]
                }
              }
            },
            "required": [
              "name",
              "description",
              "comuna",
              "address",
              "stage",
              "delivery_date",
              "subsidies",
              "photos",
              "sales_office_name",
              "tipologias"
            ],
            "description": "The facts the kit is written from. A person’s own agent reads them to write copy in another voice and sends it back with PATCH."
          }
        },
        "required": [
          "kit",
          "source"
        ]
      },
      "MarketingKitView": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "content": {
            "$ref": "#/components/schemas/MarketingKitContent"
          },
          "generated_at": {
            "type": "string"
          },
          "generated_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "edited_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null while the kit is as generated."
          },
          "edited_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "project_id",
          "content",
          "generated_at",
          "generated_by",
          "edited_at",
          "edited_by",
          "created_at",
          "updated_at"
        ]
      },
      "UpdateMarketingKitBody": {
        "type": "object",
        "properties": {
          "listing": {
            "type": "object",
            "properties": {
              "headline": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "summary": {
                "type": "string",
                "minLength": 1,
                "maxLength": 300
              },
              "body": {
                "type": "string",
                "minLength": 1,
                "maxLength": 5000
              },
              "highlights": {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 200
                },
                "maxItems": 12
              }
            },
            "required": [
              "headline",
              "summary",
              "body",
              "highlights"
            ],
            "description": "Copy for a portal listing or the proyecto’s page."
          },
          "instagram": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "caption": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2200,
                  "description": "The post’s text, hashtags apart."
                },
                "hashtags": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "pattern": "^[\\p{L}\\p{N}_]{1,60}$"
                  },
                  "maxItems": 30
                },
                "image_url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 2000,
                  "format": "uri",
                  "description": "A public image of the proyecto or a tipología; null = pick one."
                }
              },
              "required": [
                "caption",
                "hashtags",
                "image_url"
              ]
            },
            "maxItems": 10
          },
          "whatsapp": {
            "type": "object",
            "properties": {
              "message": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500,
                "description": "ONE paragraph. It fills the {{mensaje}} variable of the approved novedad_proyecto template, so it cannot hold line breaks."
              }
            },
            "required": [
              "message"
            ],
            "description": "The WhatsApp broadcast message."
          }
        },
        "additionalProperties": false
      },
      "MarketingKitAudience": {
        "type": "object",
        "properties": {
          "matched": {
            "type": "integer",
            "description": "Leads about the proyecto that inquired through Vitrina."
          },
          "capped": {
            "type": "integer",
            "description": "Of those, reached by a kit broadcast in the last 7 days."
          },
          "opted_out": {
            "type": "integer",
            "description": "Of the rest, opted out of marketing."
          },
          "eligible": {
            "type": "integer",
            "description": "Who a broadcast would reach now."
          },
          "ready": {
            "type": "boolean",
            "description": "The kit exists and the approved template is on the number."
          },
          "issue": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "no_kit",
              "no_channel",
              "no_template"
            ]
          }
        },
        "required": [
          "matched",
          "capped",
          "opted_out",
          "eligible",
          "ready",
          "issue"
        ]
      },
      "MarketingKitBroadcast": {
        "type": "object",
        "properties": {
          "campaign_id": {
            "type": "string",
            "format": "uuid",
            "description": "The marketing campaign that carries it (Campañas shows its results)."
          },
          "recipients": {
            "type": "integer"
          }
        },
        "required": [
          "campaign_id",
          "recipients"
        ]
      },
      "UnitOffer": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "listed",
              "price_drop",
              "released"
            ],
            "description": "What made the unidad news: listed | price_drop | released (a reserva fell through)."
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "property_id": {
            "type": "string",
            "format": "uuid",
            "description": "The unidad offered."
          },
          "unit_title": {
            "type": [
              "string",
              "null"
            ]
          },
          "contact_id": {
            "type": "string"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "campaign_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The marketing campaign that carried the offer (Campañas shows its results)."
          }
        },
        "required": [
          "id",
          "created_at",
          "kind",
          "project_id",
          "project_name",
          "property_id",
          "unit_title",
          "contact_id",
          "lead_id",
          "campaign_id"
        ]
      },
      "UnitOfferSettings": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "The owner’s decision to let leads that inquired hear about matching stock. Default false: nothing is sent until it is turned on."
          },
          "weekly_cap": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3,
            "description": "Offers one contact may receive per rolling 7 days (1 to 3)."
          },
          "max_inquiry_age_days": {
            "type": "integer",
            "minimum": 30,
            "maximum": 730,
            "description": "A lead whose last activity is older than this is history and hears nothing (30 to 730)."
          }
        },
        "required": [
          "enabled",
          "weekly_cap",
          "max_inquiry_age_days"
        ]
      },
      "UpdateUnitOfferSettingsBody": {
        "type": "object",
        "properties": {
          "enabled": {
            "type": "boolean",
            "description": "The owner’s decision to let leads that inquired hear about matching stock. Default false: nothing is sent until it is turned on."
          },
          "weekly_cap": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3,
            "description": "Offers one contact may receive per rolling 7 days (1 to 3)."
          },
          "max_inquiry_age_days": {
            "type": "integer",
            "minimum": 30,
            "maximum": 730,
            "description": "A lead whose last activity is older than this is history and hears nothing (30 to 730)."
          }
        },
        "additionalProperties": false
      },
      "TicketObservation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ticket_id": {
            "type": "string",
            "format": "uuid"
          },
          "partida": {
            "type": "string",
            "enum": [
              "terminaciones",
              "instalaciones_sanitarias",
              "instalaciones_electricas",
              "instalaciones_gas",
              "instalaciones_climatizacion",
              "techumbre",
              "estructura",
              "otra"
            ],
            "description": "Kind of element, which sets the legal plazo (LGUC art. 18): terminaciones 3 años from the inscripción en el CBR; instalaciones_* and techumbre 5 años and estructura 10 años from the recepción definitiva (DOM); otra = staff decide."
          },
          "description": {
            "type": "string"
          },
          "warranty_years": {
            "type": [
              "integer",
              "null"
            ]
          },
          "warranty_base": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "cbr",
              "dom"
            ],
            "description": "cbr = the plazo starts at the inscripción en el CBR; dom = at the recepción definitiva."
          },
          "warranty_start_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "warranty_expires_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "warranty_status": {
            "type": "string",
            "enum": [
              "dentro_de_plazo",
              "fuera_de_plazo",
              "por_revisar"
            ],
            "description": "Computed from the dates on record, never from the owner’s words. fuera_de_plazo and por_revisar are flagged `needs_review` for staff; the defect is still registered."
          },
          "needs_review": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ]
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "ticket_id",
          "partida",
          "description",
          "warranty_years",
          "warranty_base",
          "warranty_start_at",
          "warranty_expires_at",
          "warranty_status",
          "needs_review",
          "status",
          "created_by",
          "created_at",
          "resolved_at"
        ]
      },
      "TicketMedia": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ticket_id": {
            "type": "string",
            "format": "uuid"
          },
          "observation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "media_type": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "file"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "conversation",
              "staff",
              "contratista",
              "acta"
            ]
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "caption": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "ticket_id",
          "observation_id",
          "url",
          "media_type",
          "source",
          "message_id",
          "caption",
          "created_at"
        ]
      },
      "PostventaCase": {
        "type": "object",
        "properties": {
          "ticket_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "priority": {
            "type": "string",
            "enum": [
              "emergencia",
              "alta",
              "normal"
            ],
            "description": "emergencia (safety risk; pushes the proyecto’s on-call people) | alta | normal."
          },
          "project": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "unit": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "display_id": {
                "type": "string"
              },
              "unit_number": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "display_id",
              "unit_number",
              "kind"
            ]
          },
          "observations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketObservation"
            }
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TicketMedia"
            },
            "description": "The owner’s photos and videos from the caso’s conversations (copied in on read), plus staff uploads."
          },
          "next_visit": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "appointment_id": {
                "type": "string",
                "format": "uuid"
              },
              "starts_at": {
                "type": "string"
              },
              "ends_at": {
                "type": "string"
              },
              "status": {
                "type": "string"
              }
            },
            "required": [
              "appointment_id",
              "starts_at",
              "ends_at",
              "status"
            ]
          },
          "survey": {
            "type": "object",
            "properties": {
              "asked_at": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "score": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "The owner’s own 1 to 5."
              }
            },
            "required": [
              "asked_at",
              "score"
            ]
          }
        },
        "required": [
          "ticket_id",
          "display_id",
          "priority",
          "project",
          "unit",
          "observations",
          "media",
          "next_visit",
          "survey"
        ]
      },
      "UpdateCaseBody": {
        "type": "object",
        "properties": {
          "priority": {
            "type": "string",
            "enum": [
              "emergencia",
              "alta",
              "normal"
            ],
            "description": "emergencia (safety risk; pushes the proyecto’s on-call people) | alta | normal."
          },
          "property_id": {
            "type": "string",
            "format": "uuid",
            "description": "The unidad of the caso. Must be a unidad the ticket’s contact owns (`unit_owner`); the proyecto follows from it."
          }
        }
      },
      "CreateObservationBody": {
        "type": "object",
        "properties": {
          "partida": {
            "type": "string",
            "enum": [
              "terminaciones",
              "instalaciones_sanitarias",
              "instalaciones_electricas",
              "instalaciones_gas",
              "instalaciones_climatizacion",
              "techumbre",
              "estructura",
              "otra"
            ],
            "description": "Kind of element, which sets the legal plazo (LGUC art. 18): terminaciones 3 años from the inscripción en el CBR; instalaciones_* and techumbre 5 años and estructura 10 años from the recepción definitiva (DOM); otra = staff decide."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "years": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 10,
            "description": "Staff correction of the legal plazo in años; omit to use the law’s."
          }
        },
        "required": [
          "partida",
          "description"
        ]
      },
      "UpdateObservationBody": {
        "type": "object",
        "properties": {
          "partida": {
            "type": "string",
            "enum": [
              "terminaciones",
              "instalaciones_sanitarias",
              "instalaciones_electricas",
              "instalaciones_gas",
              "instalaciones_climatizacion",
              "techumbre",
              "estructura",
              "otra"
            ],
            "description": "Kind of element, which sets the legal plazo (LGUC art. 18): terminaciones 3 años from the inscripción en el CBR; instalaciones_* and techumbre 5 años and estructura 10 años from the recepción definitiva (DOM); otra = staff decide."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2000
          },
          "years": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 10
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "resolved"
            ]
          }
        }
      },
      "AttachCaseMediaBody": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string",
                  "maxLength": 2000,
                  "format": "uri",
                  "description": "Public URL (upload with `POST /tenant/media` first)."
                },
                "observation_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "caption": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 300
                }
              },
              "required": [
                "url"
              ]
            },
            "minItems": 1,
            "maxItems": 50
          }
        },
        "required": [
          "items"
        ]
      },
      "ScheduledCaseVisit": {
        "type": "object",
        "properties": {
          "appointment_id": {
            "type": "string",
            "format": "uuid"
          },
          "starts_at": {
            "type": "string"
          },
          "ends_at": {
            "type": "string"
          }
        },
        "required": [
          "appointment_id",
          "starts_at",
          "ends_at"
        ]
      },
      "ScheduleCaseVisitBody": {
        "type": "object",
        "properties": {
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000
          },
          "notify_owner": {
            "type": "boolean",
            "description": "Tell the owner on the caso’s conversation (default true). Scheduling again moves the visit."
          }
        },
        "required": [
          "starts_at"
        ]
      },
      "UnitOwner": {
        "type": "object",
        "properties": {
          "unit_owner_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "unit_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_name": {
            "type": "string"
          },
          "escritura_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cbr_registered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "recepcion_definitiva_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "unit_owner_id",
          "property_id",
          "display_id",
          "unit_number",
          "kind",
          "title",
          "project_id",
          "project_name",
          "escritura_at",
          "cbr_registered_at",
          "delivered_at",
          "recepcion_definitiva_at"
        ]
      },
      "UpsertUnitOwnerBody": {
        "type": "object",
        "properties": {
          "property_id": {
            "type": "string",
            "format": "uuid",
            "description": "The unidad (a property of a proyecto)."
          },
          "contact_id": {
            "type": "string",
            "format": "uuid",
            "description": "The owner: a contact of this workspace."
          },
          "escritura_at": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          },
          "cbr_registered_at": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Inscripción en el CBR. The 3-year terminaciones garantía starts here."
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
          }
        },
        "required": [
          "property_id",
          "contact_id"
        ]
      },
      "PostventaReportRow": {
        "type": "object",
        "properties": {
          "key": {
            "type": [
              "string",
              "null"
            ]
          },
          "label": {
            "type": "string"
          },
          "casos": {
            "type": "integer"
          },
          "observaciones": {
            "type": "integer"
          },
          "abiertos": {
            "type": "integer"
          },
          "resueltos": {
            "type": "integer"
          },
          "fuera_de_plazo": {
            "type": "integer"
          },
          "mediana_dias_resolucion": {
            "type": [
              "number",
              "null"
            ]
          },
          "antiguedad_max_dias": {
            "type": [
              "number",
              "null"
            ]
          },
          "encuesta_promedio": {
            "type": [
              "number",
              "null"
            ]
          }
        },
        "required": [
          "key",
          "label",
          "casos",
          "observaciones",
          "abiertos",
          "resueltos",
          "fuera_de_plazo",
          "mediana_dias_resolucion",
          "antiguedad_max_dias",
          "encuesta_promedio"
        ]
      },
      "Contratista": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "trade": {
            "type": [
              "string",
              "null"
            ]
          },
          "whatsapp": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "zones": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "trade",
          "whatsapp",
          "email",
          "zones",
          "notes",
          "active",
          "created_at",
          "updated_at"
        ]
      },
      "CreateContratistaBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "trade": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120
          },
          "whatsapp": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40,
            "description": "A WhatsApp number; stored as E.164 (`+56912345678`)."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "format": "email"
          },
          "zones": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "maxItems": 50,
            "description": "Comunas or regiones covered; a guide for staff, not a filter."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "active": {
            "type": "boolean"
          }
        },
        "required": [
          "name"
        ]
      },
      "UpdateContratistaBody": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "trade": {
            "type": [
              "string",
              "null"
            ],
            "minLength": 1,
            "maxLength": 120
          },
          "whatsapp": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 40,
            "description": "A WhatsApp number; stored as E.164 (`+56912345678`)."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 200,
            "format": "email"
          },
          "zones": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "maxItems": 50,
            "description": "Comunas or regiones covered; a guide for staff, not a filter."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "active": {
            "type": "boolean"
          }
        }
      },
      "CaseAssignment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "ticket_id": {
            "type": "string",
            "format": "uuid"
          },
          "contratista_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "assigned",
              "accepted",
              "scheduled",
              "done",
              "declined",
              "cancelled"
            ],
            "description": "assigned (sent, not yet answered) → accepted → scheduled (visit booked) → done; or declined / cancelled."
          },
          "brief": {
            "type": [
              "string",
              "null"
            ]
          },
          "owner_window": {
            "type": [
              "string",
              "null"
            ]
          },
          "appointment_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "visit_starts_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "notified_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "notify_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "accepted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "done_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "declined_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "decline_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "completion_notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "contratista_name": {
            "type": "string"
          },
          "contratista_trade": {
            "type": [
              "string",
              "null"
            ]
          },
          "contratista_whatsapp": {
            "type": [
              "string",
              "null"
            ]
          },
          "observation_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "id",
          "ticket_id",
          "contratista_id",
          "status",
          "brief",
          "owner_window",
          "appointment_id",
          "visit_starts_at",
          "notified_at",
          "notify_error",
          "accepted_at",
          "scheduled_at",
          "done_at",
          "declined_at",
          "decline_reason",
          "completion_notes",
          "created_at"
        ]
      },
      "AssignCaseResult": {
        "type": "object",
        "properties": {
          "assignment": {
            "$ref": "#/components/schemas/CaseAssignment"
          },
          "link": {
            "type": "string",
            "description": "The contratista’s no-login link (`/caso/<token>`), 30 days."
          },
          "notified": {
            "type": "boolean"
          },
          "notify_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "observation_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "assignment",
          "link",
          "notified",
          "notify_error",
          "observation_ids"
        ]
      },
      "AssignCaseBody": {
        "type": "object",
        "properties": {
          "contratista_id": {
            "type": "string",
            "format": "uuid"
          },
          "observation_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 50,
            "description": "The observaciones to send. Omitted = every open observación of the caso. An observación already with another live contratista is a 409."
          },
          "brief": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000,
            "description": "What staff want the contratista to know."
          },
          "owner_window": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 300,
            "description": "When the owner can receive the visit, in staff’s words («lunes a viernes 9 a 18»). Shown instead of the owner’s phone."
          },
          "notify": {
            "type": "boolean",
            "description": "Send the link by WhatsApp now (default true). The result says whether it went out; if not, copy the link."
          }
        },
        "required": [
          "contratista_id"
        ]
      },
      "CaseAssignmentEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "assignment_id": {
            "type": "string",
            "format": "uuid"
          },
          "ticket_id": {
            "type": "string",
            "format": "uuid"
          },
          "kind": {
            "type": "string",
            "enum": [
              "assigned",
              "notified",
              "notify_failed",
              "viewed",
              "accepted",
              "declined",
              "visit_scheduled",
              "media_added",
              "done",
              "cancelled"
            ]
          },
          "actor": {
            "type": "string",
            "enum": [
              "staff",
              "contratista",
              "system"
            ]
          },
          "data": {
            "type": "object",
            "additionalProperties": {}
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "assignment_id",
          "ticket_id",
          "kind",
          "actor",
          "data",
          "created_at"
        ]
      },
      "CaseLink": {
        "type": "object",
        "properties": {
          "link": {
            "type": "string"
          },
          "notified": {
            "type": "boolean"
          },
          "notify_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "link",
          "notified",
          "notify_error"
        ]
      },
      "CaseLinkBody": {
        "type": "object",
        "properties": {
          "resend": {
            "type": "boolean",
            "description": "Also send it to the contratista by WhatsApp."
          }
        }
      },
      "UnitHandoverListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_signature",
              "signed",
              "cancelled"
            ],
            "description": "draft (being walked) → pending_signature (sent to the owner) → signed; or cancelled."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "performed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "performed_at": {
            "type": "string"
          },
          "signer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "signature_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "whatsapp_code",
              "in_person"
            ],
            "description": "whatsapp_code: a code sent to the owner’s WhatsApp was entered. in_person: staff attested the owner’s identity."
          },
          "signed_pdf_sha256": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_pdf_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sent_for_signature_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "attested_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "project_name": {
            "type": "string"
          },
          "unit_label": {
            "type": "string"
          },
          "contact_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "items_total": {
            "type": "integer"
          },
          "items_observations": {
            "type": "integer"
          },
          "items_pending": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "project_id",
          "property_id",
          "contact_id",
          "status",
          "notes",
          "performed_by",
          "performed_at",
          "signer_name",
          "signed_at",
          "signature_method",
          "signed_pdf_sha256",
          "signed_pdf_bytes",
          "sent_for_signature_at",
          "attested_at",
          "cancelled_at",
          "created_at",
          "project_name",
          "unit_label",
          "contact_name",
          "items_total",
          "items_observations",
          "items_pending"
        ]
      },
      "UnitHandover": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "property_id": {
            "type": "string",
            "format": "uuid"
          },
          "contact_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_signature",
              "signed",
              "cancelled"
            ],
            "description": "draft (being walked) → pending_signature (sent to the owner) → signed; or cancelled."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "performed_by": {
            "type": [
              "string",
              "null"
            ]
          },
          "performed_at": {
            "type": "string"
          },
          "signer_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "signature_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "whatsapp_code",
              "in_person"
            ],
            "description": "whatsapp_code: a code sent to the owner’s WhatsApp was entered. in_person: staff attested the owner’s identity."
          },
          "signed_pdf_sha256": {
            "type": [
              "string",
              "null"
            ]
          },
          "signed_pdf_bytes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "sent_for_signature_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "attested_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "project_id",
          "property_id",
          "contact_id",
          "status",
          "notes",
          "performed_by",
          "performed_at",
          "signer_name",
          "signed_at",
          "signature_method",
          "signed_pdf_sha256",
          "signed_pdf_bytes",
          "sent_for_signature_at",
          "attested_at",
          "cancelled_at",
          "created_at"
        ]
      },
      "UnitHandoverItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "handover_id": {
            "type": "string",
            "format": "uuid"
          },
          "section": {
            "type": "string"
          },
          "item_key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "position": {
            "type": "integer"
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "ok",
              "observation",
              "na"
            ],
            "description": "pending (not walked yet) | ok (conforme) | observation (a defect, becomes a caso) | na (does not apply)."
          },
          "partida": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "terminaciones",
              "instalaciones_sanitarias",
              "instalaciones_electricas",
              "instalaciones_gas",
              "instalaciones_climatizacion",
              "techumbre",
              "estructura",
              "otra"
            ],
            "description": "Kind of element, which sets the garantía plazo of the observación it becomes (LGUC art. 18)."
          },
          "priority": {
            "type": "string",
            "enum": [
              "emergencia",
              "alta",
              "normal"
            ],
            "description": "emergencia | alta | normal."
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "split_case": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "handover_id",
          "section",
          "item_key",
          "label",
          "position",
          "state",
          "partida",
          "priority",
          "note",
          "split_case"
        ]
      },
      "UnitHandoverMedia": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "handover_id": {
            "type": "string",
            "format": "uuid"
          },
          "item_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "media_type": {
            "type": "string",
            "enum": [
              "image",
              "video",
              "file"
            ]
          },
          "caption": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "handover_id",
          "item_id",
          "url",
          "media_type",
          "caption",
          "created_at"
        ]
      },
      "UnitHandoverDetail": {
        "type": "object",
        "properties": {
          "handover": {
            "$ref": "#/components/schemas/UnitHandover"
          },
          "project": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "unit": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "label": {
                "type": "string"
              },
              "address": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "comuna": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "label",
              "address",
              "comuna"
            ]
          },
          "owner": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "id",
              "name"
            ]
          },
          "sections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "items": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/UnitHandoverItem"
                  }
                }
              },
              "required": [
                "title",
                "items"
              ]
            }
          },
          "media": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UnitHandoverMedia"
            }
          },
          "casos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_id": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "display_id"
              ]
            },
            "description": "The post-venta casos opened from the signed acta."
          },
          "counts": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer"
              },
              "ok": {
                "type": "integer"
              },
              "observation": {
                "type": "integer"
              },
              "na": {
                "type": "integer"
              },
              "pending": {
                "type": "integer"
              }
            },
            "required": [
              "total",
              "ok",
              "observation",
              "na",
              "pending"
            ]
          }
        },
        "required": [
          "handover",
          "project",
          "unit",
          "owner",
          "sections",
          "media",
          "casos",
          "counts"
        ]
      },
      "CreateHandoverBody": {
        "type": "object",
        "properties": {
          "property_id": {
            "type": "string",
            "format": "uuid",
            "description": "The unidad being handed over."
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "The owner. Omitted = the unidad’s registered owner (`unit_owner`); one of the two is required."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          }
        },
        "required": [
          "property_id"
        ]
      },
      "UpdateHandoverBody": {
        "type": "object",
        "properties": {
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 4000
          }
        },
        "required": [
          "notes"
        ]
      },
      "UpdateHandoverItemBody": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "ok",
              "observation",
              "na"
            ],
            "description": "pending (not walked yet) | ok (conforme) | observation (a defect, becomes a caso) | na (does not apply)."
          },
          "partida": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "terminaciones",
              "instalaciones_sanitarias",
              "instalaciones_electricas",
              "instalaciones_gas",
              "instalaciones_climatizacion",
              "techumbre",
              "estructura",
              "otra"
            ],
            "description": "Kind of element, which sets the garantía plazo of the observación it becomes (LGUC art. 18)."
          },
          "priority": {
            "type": "string",
            "enum": [
              "emergencia",
              "alta",
              "normal"
            ],
            "description": "emergencia | alta | normal."
          },
          "note": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000
          },
          "split_case": {
            "type": "boolean",
            "description": "true = this observación opens its own caso; otherwise all the acta’s observaciones open ONE caso with several observaciones."
          }
        }
      },
      "AttachHandoverMediaBody": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "item_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "url": {
                  "type": "string",
                  "maxLength": 2000,
                  "format": "uri",
                  "description": "Public URL (upload with `POST /tenant/media` first)."
                },
                "caption": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "maxLength": 300
                }
              },
              "required": [
                "url"
              ]
            },
            "minItems": 1,
            "maxItems": 50
          }
        },
        "required": [
          "items"
        ]
      },
      "SendHandoverResult": {
        "type": "object",
        "properties": {
          "link": {
            "type": "string",
            "description": "The owner’s no-login link (`/acta/<token>`), 14 days."
          },
          "notified": {
            "type": "boolean"
          },
          "notify_error": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "link",
          "notified",
          "notify_error"
        ]
      },
      "SendHandoverBody": {
        "type": "object",
        "properties": {
          "notify": {
            "type": "boolean",
            "description": "Send the link to the owner’s WhatsApp (default true). With false only the link comes back: show it on the staff device at the entrega."
          }
        }
      },
      "OpenHandoverCasosResult": {
        "type": "object",
        "properties": {
          "casos": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "display_id": {
                  "type": "string"
                },
                "observations": {
                  "type": "integer"
                }
              },
              "required": [
                "id",
                "display_id",
                "observations"
              ]
            }
          }
        },
        "required": [
          "casos"
        ]
      },
      "EntregaChecklist": {
        "type": "object",
        "properties": {
          "custom": {
            "type": "boolean",
            "description": "true = the proyecto’s own list; false = the platform default."
          },
          "sections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "pattern": "^[a-z0-9][a-z0-9_-]{0,59}$"
                },
                "title": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "items": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key": {
                        "type": "string",
                        "pattern": "^[a-z0-9][a-z0-9_-]{0,59}$"
                      },
                      "label": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "partida": {
                        "type": "string",
                        "enum": [
                          "terminaciones",
                          "instalaciones_sanitarias",
                          "instalaciones_electricas",
                          "instalaciones_gas",
                          "instalaciones_climatizacion",
                          "techumbre",
                          "estructura",
                          "otra"
                        ],
                        "description": "Kind of element, which sets the garantía plazo of the observación it becomes (LGUC art. 18)."
                      }
                    },
                    "required": [
                      "key",
                      "label"
                    ]
                  },
                  "minItems": 1,
                  "maxItems": 60
                }
              },
              "required": [
                "key",
                "title",
                "items"
              ]
            }
          }
        },
        "required": [
          "custom",
          "sections"
        ]
      },
      "PutEntregaChecklistBody": {
        "type": "object",
        "properties": {
          "sections": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "pattern": "^[a-z0-9][a-z0-9_-]{0,59}$"
                },
                "title": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "items": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "key": {
                        "type": "string",
                        "pattern": "^[a-z0-9][a-z0-9_-]{0,59}$"
                      },
                      "label": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "partida": {
                        "type": "string",
                        "enum": [
                          "terminaciones",
                          "instalaciones_sanitarias",
                          "instalaciones_electricas",
                          "instalaciones_gas",
                          "instalaciones_climatizacion",
                          "techumbre",
                          "estructura",
                          "otra"
                        ],
                        "description": "Kind of element, which sets the garantía plazo of the observación it becomes (LGUC art. 18)."
                      }
                    },
                    "required": [
                      "key",
                      "label"
                    ]
                  },
                  "minItems": 1,
                  "maxItems": 60
                }
              },
              "required": [
                "key",
                "title",
                "items"
              ]
            },
            "minItems": 1,
            "maxItems": 30
          }
        },
        "required": [
          "sections"
        ]
      },
      "GuestCheckoutOrder": {
        "type": "object",
        "properties": {
          "order_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_payment",
              "paid",
              "preparing",
              "ready_for_pickup",
              "shipped",
              "delivered",
              "cancelled",
              "refunded"
            ]
          },
          "total_clp": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "variant_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "sku": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "title": {
                  "type": "string"
                },
                "variant_title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "value": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "name",
                      "value"
                    ]
                  }
                },
                "quantity": {
                  "type": "integer"
                },
                "unit_price_clp": {
                  "type": "integer"
                }
              },
              "required": [
                "variant_id",
                "sku",
                "title",
                "variant_title",
                "options",
                "quantity",
                "unit_price_clp"
              ]
            }
          },
          "delivery_method": {
            "type": [
              "string",
              "null"
            ]
          },
          "shipping_clp": {
            "type": "integer",
            "description": "The delivery charge inside `total_clp` (a courier quote’s price). 0 for pickup."
          },
          "shipping": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "carrier": {
                "type": "string"
              },
              "service_name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "commune": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "region": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "estimated_business_days": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "shipment_number": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The courier’s tracking number, once dispatched."
              },
              "tracking_url": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The courier’s public tracking page, once dispatched. Link the buyer to it."
              }
            },
            "required": [
              "carrier",
              "service_name",
              "commune",
              "region",
              "estimated_business_days",
              "shipment_number",
              "tracking_url"
            ],
            "description": "Courier delivery facts the buyer may see. Null for pickup."
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "hold_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "While unpaid: when the held units are released and the link stops taking money."
          },
          "payment_failure": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "provider": {
                "type": "string",
                "enum": [
                  "bancame"
                ]
              },
              "outcome": {
                "type": "string",
                "enum": [
                  "rejected",
                  "expired"
                ]
              },
              "reason": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "RISK_REJECTED",
                  "USER_HAS_ACTIVE_LOAN",
                  "TOKEN_EXPIRED"
                ]
              }
            },
            "required": [
              "provider",
              "outcome",
              "reason"
            ],
            "description": "Why the online payment failed, when Vitrina knows (#6402). Set when a banca.me application ended `rejected` or `expired`: the order is then `cancelled` at once and its held units are back on sale, so the buyer can start a new checkout with another method. `reason` is banca.me’s code when Vitrina learns it: `TOKEN_EXPIRED` for an expired session; `null` for a rejection read from banca.me’s API, which does not say why (risk rejection and an active loan look the same there). A buyer refused at checkout creation for an existing loan gets 409 `payment_buyer_has_active_loan` instead. Null for every other order.",
            "example": null
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "order_id",
          "display_id",
          "status",
          "total_clp",
          "currency",
          "lines",
          "delivery_method",
          "shipping_clp",
          "shipping",
          "paid_at",
          "cancelled_at",
          "hold_expires_at",
          "payment_failure",
          "created_at"
        ]
      },
      "GuestCheckout": {
        "type": "object",
        "properties": {
          "order": {
            "$ref": "#/components/schemas/GuestCheckoutOrder"
          },
          "checkout": {
            "type": "object",
            "properties": {
              "checkout_id": {
                "type": "string",
                "format": "uuid"
              },
              "checkout_url": {
                "type": "string",
                "description": "Send the buyer here (a plain redirect, whatever the provider): Vitrina’s tracked link, which records the server-side InitiateCheckout and then redirects to the Mercado Pago checkout, posts the buyer to the Webpay form, or redirects to banca.me’s page."
              },
              "provider": {
                "type": "string",
                "enum": [
                  "mercadopago",
                  "webpay",
                  "bancame"
                ],
                "description": "The provider the link charges through."
              },
              "amount_clp": {
                "type": "integer"
              },
              "expires_at": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "checkout_id",
              "checkout_url",
              "provider",
              "amount_clp",
              "expires_at"
            ]
          },
          "hold": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "expires_at": {
                "type": "string"
              },
              "variant_ids": {
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "uuid"
                }
              }
            },
            "required": [
              "expires_at",
              "variant_ids"
            ]
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "replayed": {
            "type": "boolean"
          }
        },
        "required": [
          "order",
          "checkout",
          "hold",
          "contact_id",
          "replayed"
        ]
      },
      "StorefrontPaymentProviders": {
        "type": "object",
        "properties": {
          "default": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "mercadopago",
              "webpay",
              "bancame"
            ],
            "description": "The provider a checkout without `payment_provider` uses; null when none is connected (checkout answers 409 `payment_not_connected`)."
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "provider": {
                  "type": "string",
                  "enum": [
                    "mercadopago",
                    "webpay",
                    "bancame"
                  ]
                },
                "name": {
                  "type": "string",
                  "example": "Webpay"
                }
              },
              "required": [
                "provider",
                "name"
              ]
            },
            "description": "The providers the tenant connected, in the order to offer them."
          }
        },
        "required": [
          "default",
          "providers"
        ]
      },
      "CommerceOrder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "native",
              "shopify",
              "woocommerce"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the number the store shows (it can differ from `external_id`). Null for a native order."
          },
          "store_payment_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the store's own payment page, where the buyer pays with the store's gateways. Null for a native order."
          },
          "external_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the store's modification time as of the last applied store event."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_payment",
              "paid",
              "preparing",
              "ready_for_pickup",
              "shipped",
              "delivered",
              "cancelled",
              "refunded"
            ]
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "obligation_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "currency": {
            "type": "string"
          },
          "delivery": {
            "type": "object",
            "additionalProperties": {}
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "shipping_clp": {
            "type": "integer",
            "description": "What the order charges for delivery (a courier quote frozen at checkout, `delivery.method = shipping`). Included in `total_clp`."
          },
          "total_clp": {
            "type": "integer",
            "description": "Σ of the lines + `shipping_clp`."
          },
          "item_count": {
            "type": "integer"
          },
          "stock_decremented_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ready_for_pickup_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "A pickup order: when the store marked it ready («Listo para retiro»), first time."
          },
          "shipped_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "refunded_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a paid order was refunded in full (`status: refunded`)."
          },
          "refund_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "refund_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payment provider refund id (Mercado Pago refund id, or the Webpay refund authorization code), when returned."
          },
          "buyer": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "A storefront checkout’s buyer as declared at purchase (`POST /orders/checkout`). `{}` for orders created by staff or the agent: the contact is the buyer."
          },
          "attribution": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "A storefront checkout’s ad touch (utm_*, fbclid, gclid, anonymous_id, fbp, fbc). `{}` otherwise."
          },
          "client_event_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "A storefront checkout’s browser InitiateCheckout `eventID` (the server InitiateCheckout and Purchase reuse it). Null otherwise."
          },
          "return_urls": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "success": {
                "type": "string"
              },
              "failure": {
                "type": "string"
              },
              "pending": {
                "type": "string"
              }
            },
            "required": [
              "success",
              "failure",
              "pending"
            ],
            "description": "A storefront checkout’s return pages. Null = the Vitrina receipt page."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "position": {
                  "type": "integer"
                },
                "product_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "variant_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "title": {
                  "type": "string"
                },
                "variant_title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "variant_options": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "value": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "name",
                      "value"
                    ]
                  },
                  "description": "The variant’s option values frozen at creation (`[{name: \"Color\", value: \"Negro\"}]`). Empty for a simple product and for lines created before 2026-10-29."
                },
                "sku": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "unit_price_clp": {
                  "type": "integer"
                },
                "quantity": {
                  "type": "integer"
                },
                "line_total_clp": {
                  "type": "integer"
                },
                "obligation_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                }
              },
              "required": [
                "id",
                "position",
                "product_id",
                "variant_id",
                "title",
                "variant_title",
                "variant_options",
                "sku",
                "unit_price_clp",
                "quantity",
                "line_total_clp",
                "obligation_id"
              ]
            }
          }
        },
        "required": [
          "id",
          "tenant_id",
          "display_id",
          "source",
          "external_id",
          "external_number",
          "store_payment_url",
          "external_updated_at",
          "status",
          "contact_id",
          "conversation_id",
          "lead_id",
          "obligation_group_id",
          "currency",
          "delivery",
          "notes",
          "shipping_clp",
          "total_clp",
          "item_count",
          "stock_decremented_at",
          "paid_at",
          "ready_for_pickup_at",
          "shipped_at",
          "delivered_at",
          "cancelled_at",
          "cancelled_reason",
          "refunded_at",
          "refund_reason",
          "refund_reference",
          "buyer",
          "attribution",
          "client_event_id",
          "return_urls",
          "created_by",
          "created_at",
          "updated_at",
          "lines"
        ]
      },
      "OrderBuyerEmail": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "order_confirmed",
              "ready_for_pickup",
              "shipped",
              "delivered"
            ],
            "description": "Which step: order_confirmed (paid), ready_for_pickup, shipped, delivered."
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "delivered",
              "failed",
              "bounced",
              "complained"
            ],
            "description": "`pending` while sending; `sent` accepted by the mail provider; `delivered` the buyer’s server took it; `failed` not sent (retried while attempts remain); `bounced` / `complained` reported back by the provider."
          },
          "to": {
            "type": "string",
            "description": "The buyer address it went to."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "kind",
          "status",
          "to",
          "sent_at",
          "delivered_at",
          "failed_at",
          "attempts",
          "created_at"
        ]
      },
      "CommerceOrderDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "display_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "native",
              "shopify",
              "woocommerce"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the number the store shows (it can differ from `external_id`). Null for a native order."
          },
          "store_payment_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the store's own payment page, where the buyer pays with the store's gateways. Null for a native order."
          },
          "external_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "A store order: the store's modification time as of the last applied store event."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "pending_payment",
              "paid",
              "preparing",
              "ready_for_pickup",
              "shipped",
              "delivered",
              "cancelled",
              "refunded"
            ]
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "conversation_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "lead_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "obligation_group_id": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "currency": {
            "type": "string"
          },
          "delivery": {
            "type": "object",
            "additionalProperties": {}
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "shipping_clp": {
            "type": "integer",
            "description": "What the order charges for delivery (a courier quote frozen at checkout, `delivery.method = shipping`). Included in `total_clp`."
          },
          "total_clp": {
            "type": "integer",
            "description": "Σ of the lines + `shipping_clp`."
          },
          "item_count": {
            "type": "integer"
          },
          "stock_decremented_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "ready_for_pickup_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "A pickup order: when the store marked it ready («Listo para retiro»), first time."
          },
          "shipped_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "cancelled_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "refunded_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a paid order was refunded in full (`status: refunded`)."
          },
          "refund_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "refund_reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "The payment provider refund id (Mercado Pago refund id, or the Webpay refund authorization code), when returned."
          },
          "buyer": {
            "type": "object",
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "rut": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "description": "A storefront checkout’s buyer as declared at purchase (`POST /orders/checkout`). `{}` for orders created by staff or the agent: the contact is the buyer."
          },
          "attribution": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "A storefront checkout’s ad touch (utm_*, fbclid, gclid, anonymous_id, fbp, fbc). `{}` otherwise."
          },
          "client_event_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "A storefront checkout’s browser InitiateCheckout `eventID` (the server InitiateCheckout and Purchase reuse it). Null otherwise."
          },
          "return_urls": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "success": {
                "type": "string"
              },
              "failure": {
                "type": "string"
              },
              "pending": {
                "type": "string"
              }
            },
            "required": [
              "success",
              "failure",
              "pending"
            ],
            "description": "A storefront checkout’s return pages. Null = the Vitrina receipt page."
          },
          "created_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "format": "uuid"
                },
                "position": {
                  "type": "integer"
                },
                "product_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "variant_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "title": {
                  "type": "string"
                },
                "variant_title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "variant_options": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "value": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "name",
                      "value"
                    ]
                  },
                  "description": "The variant’s option values frozen at creation (`[{name: \"Color\", value: \"Negro\"}]`). Empty for a simple product and for lines created before 2026-10-29."
                },
                "sku": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "unit_price_clp": {
                  "type": "integer"
                },
                "quantity": {
                  "type": "integer"
                },
                "line_total_clp": {
                  "type": "integer"
                },
                "obligation_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                }
              },
              "required": [
                "id",
                "position",
                "product_id",
                "variant_id",
                "title",
                "variant_title",
                "variant_options",
                "sku",
                "unit_price_clp",
                "quantity",
                "line_total_clp",
                "obligation_id"
              ]
            }
          },
          "buyer_emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderBuyerEmail"
            },
            "description": "The emails this order’s storefront buyer got, oldest first (#6270): at most one per kind. `[]` for orders without a buyer email (staff/agent orders hear in their conversation). Only on `GET /orders/{id}`."
          }
        },
        "required": [
          "id",
          "tenant_id",
          "display_id",
          "source",
          "external_id",
          "external_number",
          "store_payment_url",
          "external_updated_at",
          "status",
          "contact_id",
          "conversation_id",
          "lead_id",
          "obligation_group_id",
          "currency",
          "delivery",
          "notes",
          "shipping_clp",
          "total_clp",
          "item_count",
          "stock_decremented_at",
          "paid_at",
          "ready_for_pickup_at",
          "shipped_at",
          "delivered_at",
          "cancelled_at",
          "cancelled_reason",
          "refunded_at",
          "refund_reason",
          "refund_reference",
          "buyer",
          "attribution",
          "client_event_id",
          "return_urls",
          "created_by",
          "created_at",
          "updated_at",
          "lines",
          "buyer_emails"
        ]
      },
      "OrderRefundResult": {
        "type": "object",
        "properties": {
          "order": {
            "$ref": "#/components/schemas/CommerceOrder"
          },
          "refunded": {
            "type": "boolean",
            "description": "`true` when this call refunded the order, `false` when it was already refunded (a replay changes nothing)."
          },
          "restored_units": {
            "type": "integer",
            "description": "Units put back on hand by this refund. 0 when another system owns the workspace on-hand count (it receives `order.status_changed` and writes the count itself) or on a replay."
          }
        },
        "required": [
          "order",
          "refunded",
          "restored_units"
        ]
      },
      "ShippingCommune": {
        "type": "object",
        "properties": {
          "coverage_code": {
            "type": "string",
            "example": "CONC"
          },
          "name": {
            "type": "string",
            "example": "Concepcion"
          },
          "commune": {
            "type": "string"
          },
          "region_code": {
            "type": "string",
            "example": "R8"
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "commune",
              "locality"
            ]
          }
        },
        "required": [
          "coverage_code",
          "name",
          "commune",
          "region_code",
          "region",
          "kind"
        ]
      },
      "ShippingQuote": {
        "type": "object",
        "properties": {
          "quote_id": {
            "type": "string",
            "format": "uuid"
          },
          "carrier": {
            "type": "string",
            "enum": [
              "chilexpress"
            ]
          },
          "destination": {
            "type": "object",
            "properties": {
              "coverage_code": {
                "type": "string"
              },
              "commune": {
                "type": "string"
              },
              "region_code": {
                "type": "string"
              },
              "region": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "coverage_code",
              "commune",
              "region_code",
              "region"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "service_code": {
                  "type": "integer",
                  "description": "Chilexpress service type: 2 Prioritario, 3 Express (día hábil siguiente), 4 Extendido, 5 Extremos, 41/42 encomiendas grandes."
                },
                "service_name": {
                  "type": "string"
                },
                "price_clp": {
                  "type": "integer"
                },
                "estimated_business_days": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "An estimate implied by the service and the region (Chilexpress gives no transit time)."
                }
              },
              "required": [
                "service_code",
                "service_name",
                "price_clp",
                "estimated_business_days"
              ]
            },
            "description": "Cheapest first."
          },
          "expires_at": {
            "type": "string",
            "description": "Send `quote_id` to `POST /orders/checkout` before this."
          }
        },
        "required": [
          "quote_id",
          "carrier",
          "destination",
          "options",
          "expires_at"
        ]
      },
      "CommerceShipment": {
        "type": "object",
        "properties": {
          "carrier": {
            "type": "string",
            "enum": [
              "chilexpress"
            ]
          },
          "environment": {
            "type": "string"
          },
          "shipment_number": {
            "type": "string",
            "description": "Chilexpress transport order (OT) number.",
            "example": "712678918615"
          },
          "tracking_url": {
            "type": "string",
            "description": "Chilexpress’s public tracking page for this shipment."
          },
          "service_code": {
            "type": "integer"
          },
          "service_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "reference": {
            "type": "string",
            "description": "The order’s display id, printed as the reference."
          },
          "has_label": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          }
        },
        "required": [
          "carrier",
          "environment",
          "shipment_number",
          "tracking_url",
          "service_code",
          "service_name",
          "reference",
          "has_label",
          "created_at"
        ]
      },
      "ChilexpressConnection": {
        "type": "object",
        "properties": {
          "connected": {
            "type": "boolean"
          },
          "environment": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "test",
              "production"
            ]
          },
          "origin_coverage_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "origin_commune": {
            "type": [
              "string",
              "null"
            ]
          },
          "origin_region_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "customer_card_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "keys": {
            "type": "object",
            "properties": {
              "rating": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "shipments": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "coverage": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "rating",
              "shipments",
              "coverage"
            ],
            "description": "Masked previews of the stored keys (never the keys)."
          },
          "can_dispatch": {
            "type": "boolean",
            "description": "True once a TCC and the Envíos key are stored."
          },
          "sender": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "email": {
                "type": "string"
              }
            },
            "required": [
              "name",
              "phone",
              "email"
            ]
          },
          "return_address": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "street": {
                "type": "string"
              },
              "number": {
                "type": "string"
              },
              "supplement": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "street",
              "number",
              "supplement"
            ]
          },
          "package": {
            "type": "object",
            "properties": {
              "weight_kg": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 100
              },
              "height_cm": {
                "type": "integer",
                "minimum": 1,
                "maximum": 300
              },
              "width_cm": {
                "type": "integer",
                "minimum": 1,
                "maximum": 300
              },
              "length_cm": {
                "type": "integer",
                "minimum": 1,
                "maximum": 300
              }
            },
            "required": [
              "weight_kg",
              "height_cm",
              "width_cm",
              "length_cm"
            ],
            "additionalProperties": false
          },
          "services": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "declared_content": {
            "type": "string",
            "enum": [
              "celular",
              "tecnologia",
              "articulos_personales",
              "educacion",
              "vestuario",
              "otros"
            ],
            "description": "What the parcel contains, as Chilexpress declares it on the label."
          },
          "seller_rut": {
            "type": [
              "string",
              "null"
            ]
          },
          "connected_at": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "connected",
          "environment",
          "origin_coverage_code",
          "origin_commune",
          "origin_region_code",
          "customer_card_number",
          "keys",
          "can_dispatch",
          "sender",
          "return_address",
          "package",
          "services",
          "declared_content",
          "seller_rut",
          "connected_at"
        ]
      },
      "ShippingEstimate": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "quoted",
              "needs_commune",
              "not_covered",
              "unknown_item"
            ]
          },
          "carrier": {
            "type": "string",
            "enum": [
              "chilexpress"
            ]
          },
          "destination": {
            "type": "object",
            "properties": {
              "coverage_code": {
                "type": "string"
              },
              "commune": {
                "type": "string"
              },
              "region_code": {
                "type": "string"
              },
              "region": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "coverage_code",
              "commune",
              "region_code",
              "region"
            ]
          },
          "options": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "service_code": {
                  "type": "integer",
                  "description": "Chilexpress service type: 2 Prioritario, 3 Express (día hábil siguiente), 4 Extendido, 5 Extremos, 41/42 encomiendas grandes."
                },
                "service_name": {
                  "type": "string"
                },
                "price_clp": {
                  "type": "integer"
                },
                "estimated_business_days": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              },
              "required": [
                "service_code",
                "service_name",
                "price_clp",
                "estimated_business_days"
              ]
            },
            "description": "`quoted`: cheapest first."
          },
          "item": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "title": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "quantity": {
                "type": "integer"
              },
              "declared_value_clp": {
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "title",
              "quantity",
              "declared_value_clp"
            ]
          },
          "declared": {
            "type": "object",
            "properties": {
              "value_clp": {
                "type": "integer",
                "exclusiveMinimum": 0
              },
              "basis": {
                "type": "string",
                "enum": [
                  "item",
                  "catalog_median",
                  "minimum"
                ]
              }
            },
            "required": [
              "value_clp",
              "basis"
            ],
            "description": "`quoted`: the value declared to Chilexpress (it changes the price): the item’s catalog price × quantity, else the median price of the store’s active catalog × quantity, else $1."
          },
          "package": {
            "type": "object",
            "properties": {
              "weight_kg": {
                "type": "number"
              },
              "height_cm": {
                "type": "number"
              },
              "width_cm": {
                "type": "number"
              },
              "length_cm": {
                "type": "number"
              }
            },
            "required": [
              "weight_kg",
              "height_cm",
              "width_cm",
              "length_cm"
            ]
          },
          "units": {
            "type": "integer"
          },
          "reason": {
            "type": "string",
            "enum": [
              "missing",
              "ambiguous",
              "region_mismatch",
              "not_found"
            ],
            "description": "`needs_commune`: why no commune was chosen."
          },
          "candidates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "coverage_code": {
                  "type": "string"
                },
                "commune": {
                  "type": "string"
                },
                "region_code": {
                  "type": "string"
                },
                "region": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "coverage_code",
                "commune",
                "region_code",
                "region"
              ]
            },
            "description": "`needs_commune`: the areas that fit (or close names); ask the buyer, then send the chosen `coverage_code`."
          }
        },
        "required": [
          "status"
        ]
      },
      "ClaimComprobanteUpload": {
        "type": "object",
        "properties": {
          "comprobante": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ClaimComprobante"
              },
              {
                "type": "object"
              }
            ]
          },
          "replaced": {
            "type": "boolean",
            "description": "True when it replaced the claim’s previous file."
          }
        },
        "required": [
          "comprobante",
          "replaced"
        ]
      },
      "WhatsappTemplateAnalytics": {
        "type": "object",
        "properties": {
          "template": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              },
              "language": {
                "type": "string"
              },
              "category": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "messaging_account_id": {
                "type": "string",
                "format": "uuid"
              }
            },
            "required": [
              "id",
              "name",
              "language",
              "category",
              "status",
              "messaging_account_id"
            ]
          },
          "range": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "description": "ISO instant (inclusive)."
              },
              "to": {
                "type": "string",
                "description": "ISO instant (exclusive)."
              },
              "timezone": {
                "type": "string",
                "description": "The workspace time zone the days are cut in."
              },
              "group_by": {
                "type": "string",
                "enum": [
                  "day",
                  "week"
                ]
              }
            },
            "required": [
              "from",
              "to",
              "timezone",
              "group_by"
            ]
          },
          "totals": {
            "type": "object",
            "properties": {
              "sent": {
                "type": "integer",
                "description": "Template sends Meta accepted."
              },
              "delivered": {
                "type": "integer",
                "description": "Of them, delivered (or read) per the delivery receipts."
              },
              "read": {
                "type": "integer"
              },
              "failed": {
                "type": "integer",
                "description": "Accepted, then reported failed by Meta (receipt)."
              },
              "replied": {
                "type": "integer",
                "description": "The person wrote on the same number within 72 h and this was their latest template (last touch)."
              },
              "opted_out": {
                "type": "integer",
                "description": "Sends after which the number opted out of WhatsApp within 72 h."
              }
            },
            "required": [
              "sent",
              "delivered",
              "read",
              "failed",
              "replied",
              "opted_out"
            ]
          },
          "rates": {
            "type": "object",
            "properties": {
              "delivered": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "delivered / sent"
              },
              "read": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "read / delivered"
              },
              "replied": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "replied / sent"
              },
              "failed": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "failed / sent"
              }
            },
            "required": [
              "delivered",
              "read",
              "replied",
              "failed"
            ],
            "description": "0–1 fractions; null when the denominator is 0."
          },
          "by_sender": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sender": {
                  "type": "string",
                  "enum": [
                    "campaign",
                    "followup",
                    "recontacto",
                    "first_touch",
                    "continuation",
                    "clinic",
                    "manual",
                    "test",
                    "other"
                  ],
                  "description": "campaign, followup (Seguimientos), recontacto, first_touch, continuation (Continuación por WhatsApp), clinic (reminders and notices), manual (inbox / copilot / API), test (campaign test sends), other (staff notices, digests, sends before 2026-10-14 that could not be told apart)."
                },
                "sent": {
                  "type": "integer",
                  "description": "Template sends Meta accepted."
                },
                "delivered": {
                  "type": "integer",
                  "description": "Of them, delivered (or read) per the delivery receipts."
                },
                "read": {
                  "type": "integer"
                },
                "failed": {
                  "type": "integer",
                  "description": "Accepted, then reported failed by Meta (receipt)."
                },
                "replied": {
                  "type": "integer",
                  "description": "The person wrote on the same number within 72 h and this was their latest template (last touch)."
                }
              },
              "required": [
                "sender",
                "sent",
                "delivered",
                "read",
                "failed",
                "replied"
              ]
            },
            "description": "Who sent the template, most sends first."
          },
          "buttons": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "text": {
                  "type": "string"
                },
                "count": {
                  "type": "integer"
                }
              },
              "required": [
                "text",
                "count"
              ]
            },
            "description": "Replies whose first message was a tap on one of the template's quick-reply buttons (every button listed, zeros included)."
          },
          "free_text": {
            "type": "integer",
            "description": "Replies whose first message was not a button tap."
          },
          "failures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string"
                },
                "title": {
                  "type": "string"
                },
                "human_es": {
                  "type": "string"
                },
                "human_en": {
                  "type": "string"
                },
                "action": {
                  "type": "string",
                  "enum": [
                    "none",
                    "remove_number",
                    "retry_later",
                    "check_template"
                  ]
                },
                "count": {
                  "type": "integer"
                }
              },
              "required": [
                "code",
                "title",
                "human_es",
                "human_en",
                "action",
                "count"
              ]
            },
            "description": "Failed sends by Meta error code, with a plain-language reason."
          },
          "daily": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "description": "Bucket start (YYYY-MM-DD, workspace time zone)."
                },
                "sent": {
                  "type": "integer",
                  "description": "Template sends Meta accepted."
                },
                "delivered": {
                  "type": "integer",
                  "description": "Of them, delivered (or read) per the delivery receipts."
                },
                "read": {
                  "type": "integer"
                },
                "failed": {
                  "type": "integer",
                  "description": "Accepted, then reported failed by Meta (receipt)."
                },
                "replied": {
                  "type": "integer",
                  "description": "The person wrote on the same number within 72 h and this was their latest template (last touch)."
                }
              },
              "required": [
                "date",
                "sent",
                "delivered",
                "read",
                "failed",
                "replied"
              ]
            },
            "description": "Every day (or week) of the range by send time, zeros included."
          }
        },
        "required": [
          "template",
          "range",
          "totals",
          "rates",
          "by_sender",
          "buttons",
          "free_text",
          "failures",
          "daily"
        ]
      },
      "WhatsappTemplateAnalyticsSummary": {
        "type": "object",
        "properties": {
          "range": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "description": "ISO instant (inclusive)."
              },
              "to": {
                "type": "string",
                "description": "ISO instant (exclusive)."
              },
              "timezone": {
                "type": "string",
                "description": "The workspace time zone the days are cut in."
              }
            },
            "required": [
              "from",
              "to",
              "timezone"
            ]
          },
          "templates": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "template_id": {
                  "type": "string",
                  "format": "uuid"
                },
                "sent": {
                  "type": "integer",
                  "description": "Template sends Meta accepted."
                },
                "delivered": {
                  "type": "integer",
                  "description": "Of them, delivered (or read) per the delivery receipts."
                },
                "read": {
                  "type": "integer"
                },
                "failed": {
                  "type": "integer",
                  "description": "Accepted, then reported failed by Meta (receipt)."
                },
                "replied": {
                  "type": "integer",
                  "description": "The person wrote on the same number within 72 h and this was their latest template (last touch)."
                },
                "opted_out": {
                  "type": "integer"
                }
              },
              "required": [
                "template_id",
                "sent",
                "delivered",
                "read",
                "failed",
                "replied",
                "opted_out"
              ]
            },
            "description": "Templates with at least one send in the range, most sends first. A template absent here sent nothing."
          }
        },
        "required": [
          "range",
          "templates"
        ]
      },
      "WhatsappFlowCondition": {
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "minLength": 1,
            "maxLength": 60,
            "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
          },
          "op": {
            "type": "string",
            "enum": [
              "equals",
              "not_equals",
              "in",
              "filled",
              "empty"
            ]
          },
          "value": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              {
                "type": "boolean"
              },
              {
                "type": "array",
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 120
                },
                "minItems": 1,
                "maxItems": 20
              }
            ]
          }
        },
        "required": [
          "field",
          "op"
        ],
        "additionalProperties": false
      },
      "WhatsappFlowComponent": {
        "oneOf": [
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "context_summary"
                ]
              },
              "template": {
                "type": "string",
                "maxLength": 1000
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "heading"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "subheading"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "body"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 4096
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "caption"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 409
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "image"
                ]
              },
              "src": {
                "type": "string",
                "minLength": 1,
                "maxLength": 450000
              },
              "alt_text": {
                "type": "string",
                "maxLength": 200
              },
              "scale_type": {
                "type": "string",
                "enum": [
                  "cover",
                  "contain"
                ]
              },
              "aspect_ratio": {
                "type": "number",
                "exclusiveMinimum": 0,
                "maximum": 10
              },
              "height": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 2000
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "src"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "rich_text"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 20000
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "embedded_link"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1,
                "maxLength": 25
              },
              "url": {
                "type": "string",
                "maxLength": 2000,
                "format": "uri"
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "text",
              "url"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "image_carousel"
                ]
              },
              "images": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "src": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 450000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "src"
                  ]
                },
                "minItems": 1,
                "maxItems": 3
              },
              "aspect": {
                "type": "string",
                "enum": [
                  "4:3",
                  "16:9"
                ]
              },
              "scale_type": {
                "type": "string",
                "enum": [
                  "cover",
                  "contain"
                ]
              }
            },
            "required": [
              "kind",
              "images"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "text_input"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 20
              },
              "required": {
                "type": "boolean"
              },
              "input_type": {
                "type": "string",
                "enum": [
                  "text",
                  "number",
                  "email",
                  "phone",
                  "password",
                  "passcode"
                ]
              },
              "helper_text": {
                "type": "string",
                "maxLength": 80
              },
              "pattern": {
                "type": "string",
                "minLength": 1,
                "maxLength": 500
              },
              "pattern_error": {
                "type": "string",
                "maxLength": 80
              },
              "min_chars": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 4096
              },
              "max_chars": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 4096
              },
              "preset": {
                "type": "string",
                "enum": [
                  "email",
                  "phone",
                  "rut",
                  "cpf",
                  "dni_ar",
                  "dni_pe",
                  "patente"
                ]
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "textarea"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 20
              },
              "required": {
                "type": "boolean"
              },
              "helper_text": {
                "type": "string",
                "maxLength": 80
              },
              "max_length": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 4096
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "dropdown"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 20
              },
              "required": {
                "type": "boolean"
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 300
                    },
                    "image": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 140000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                },
                "maxItems": 200
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "branches",
                      "appointment_types",
                      "services",
                      "professionals",
                      "inventory",
                      "vehicles",
                      "available_slots"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label",
              "options"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "radio"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 30
              },
              "required": {
                "type": "boolean"
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 300
                    },
                    "image": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 140000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                },
                "maxItems": 20
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "branches",
                      "appointment_types",
                      "services",
                      "professionals",
                      "inventory",
                      "vehicles",
                      "available_slots"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label",
              "options"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "checkbox"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 30
              },
              "required": {
                "type": "boolean"
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 300
                    },
                    "image": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 140000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                },
                "maxItems": 20
              },
              "min_selected": {
                "type": "integer",
                "minimum": 1
              },
              "max_selected": {
                "type": "integer",
                "minimum": 1
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "branches",
                      "appointment_types",
                      "services",
                      "professionals",
                      "inventory",
                      "vehicles",
                      "available_slots"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label",
              "options"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "chips"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "required": {
                "type": "boolean"
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "options": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 300
                    },
                    "image": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 140000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                },
                "maxItems": 20
              },
              "min_selected": {
                "type": "integer",
                "minimum": 1,
                "maximum": 20
              },
              "max_selected": {
                "type": "integer",
                "minimum": 1,
                "maximum": 20
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "branches",
                      "appointment_types",
                      "services",
                      "professionals",
                      "inventory",
                      "vehicles",
                      "available_slots"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label",
              "options"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "date_picker"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              },
              "required": {
                "type": "boolean"
              },
              "helper_text": {
                "type": "string",
                "maxLength": 80
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "available_dates"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              },
              "min_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "max_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "unavailable_dates": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                },
                "maxItems": 400
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "calendar_picker"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              },
              "required": {
                "type": "boolean"
              },
              "helper_text": {
                "type": "string",
                "maxLength": 80
              },
              "mode": {
                "type": "string",
                "enum": [
                  "single",
                  "range"
                ]
              },
              "min_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "max_date": {
                "type": "string",
                "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
              },
              "unavailable_dates": {
                "type": "array",
                "items": {
                  "type": "string",
                  "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                },
                "maxItems": 400
              },
              "include_days": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "Mon",
                    "Tue",
                    "Wed",
                    "Thu",
                    "Fri",
                    "Sat",
                    "Sun"
                  ]
                },
                "minItems": 1,
                "maxItems": 7
              },
              "min_days": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 366
              },
              "max_days": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 366
              },
              "start_label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              },
              "end_label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 40
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "opt_in"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "required": {
                "type": "boolean"
              },
              "read_more_url": {
                "type": "string",
                "maxLength": 2000,
                "format": "uri"
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "required_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              },
              "sensitive": {
                "type": "boolean"
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "hidden",
                      "locked",
                      "editable"
                    ]
                  },
                  "ask_if_missing": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "mode"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "navigation_list"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "items": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 30
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 80
                    },
                    "image": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 140000
                    },
                    "alt_text": {
                      "type": "string",
                      "maxLength": 200
                    }
                  },
                  "required": [
                    "id",
                    "title"
                  ]
                },
                "maxItems": 20
              },
              "source": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "enum": [
                      "branches",
                      "appointment_types",
                      "services",
                      "professionals",
                      "inventory",
                      "vehicles",
                      "available_slots"
                    ]
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 60,
                              "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
                            }
                          },
                          "required": [
                            "field"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "value": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "value"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "context": {
                              "type": "string",
                              "pattern": "^(subject|location|appointment_type|staff|service|contact\\.(name|phone|email|document)|(contact_attr|conversation_attr):[a-z0-9_.-]{1,64})$"
                            }
                          },
                          "required": [
                            "context"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    }
                  }
                },
                "required": [
                  "id"
                ]
              }
            },
            "required": [
              "kind",
              "name",
              "items"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "photo_picker"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "photo_source": {
                "type": "string",
                "enum": [
                  "camera_gallery",
                  "camera",
                  "gallery"
                ]
              },
              "min_uploaded": {
                "type": "integer",
                "minimum": 0,
                "maximum": 30
              },
              "max_uploaded": {
                "type": "integer",
                "minimum": 1,
                "maximum": 30
              },
              "max_file_size_kb": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 25600
              },
              "sensitive": {
                "type": "boolean"
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          },
          {
            "type": "object",
            "properties": {
              "kind": {
                "type": "string",
                "enum": [
                  "document_picker"
                ]
              },
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 60,
                "pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
              },
              "label": {
                "type": "string",
                "minLength": 1,
                "maxLength": 80
              },
              "description": {
                "type": "string",
                "maxLength": 300
              },
              "min_uploaded": {
                "type": "integer",
                "minimum": 0,
                "maximum": 30
              },
              "max_uploaded": {
                "type": "integer",
                "minimum": 1,
                "maximum": 30
              },
              "max_file_size_kb": {
                "type": "integer",
                "exclusiveMinimum": 0,
                "maximum": 25600
              },
              "allowed_mime_types": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "application/gzip",
                    "application/msword",
                    "application/pdf",
                    "application/vnd.ms-excel",
                    "application/vnd.ms-powerpoint",
                    "application/vnd.oasis.opendocument.presentation",
                    "application/vnd.oasis.opendocument.spreadsheet",
                    "application/vnd.oasis.opendocument.text",
                    "application/vnd.openxmlformats-officedocument.presentationml.presentation",
                    "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
                    "application/x-7z-compressed",
                    "application/zip",
                    "image/avif",
                    "image/gif",
                    "image/heic",
                    "image/heif",
                    "image/jpeg",
                    "image/png",
                    "image/tiff",
                    "image/webp",
                    "text/plain",
                    "video/mp4",
                    "video/mpeg"
                  ]
                },
                "minItems": 1
              },
              "sensitive": {
                "type": "boolean"
              },
              "visible_when": {
                "$ref": "#/components/schemas/WhatsappFlowCondition"
              }
            },
            "required": [
              "kind",
              "name",
              "label"
            ]
          }
        ]
      },
      "WhatsappFlowHealthAlert": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "status",
              "error_rate",
              "latency",
              "availability",
              "version_expiry"
            ]
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "endpoint",
              "client"
            ],
            "description": "error_rate only: the endpoint’s errors or the client’s."
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "activated",
              "deactivated"
            ],
            "description": "activated = threshold crossed; deactivated = recovered."
          },
          "value": {
            "type": [
              "number",
              "null"
            ],
            "description": "Error rate %, p90 latency ms or availability %."
          },
          "threshold": {
            "type": [
              "number",
              "null"
            ]
          },
          "window": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta’s detection window (`60m`, `30m`, `10m`)."
          },
          "meta_event": {
            "type": "string",
            "description": "Raw Meta event name."
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Meta’s own sentence."
          },
          "details": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {}
          },
          "old_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "new_status": {
            "type": [
              "string",
              "null"
            ]
          },
          "occurred_at": {
            "type": "string"
          }
        },
        "required": [
          "type",
          "source",
          "state",
          "value",
          "threshold",
          "window",
          "meta_event",
          "message",
          "details",
          "occurred_at"
        ]
      },
      "WhatsappManagedFlow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The managed flow (local row) id."
          },
          "messaging_account_id": {
            "type": "string",
            "format": "uuid",
            "description": "The WhatsApp number (messaging account) the flow lives on."
          },
          "meta_flow_id": {
            "type": "string",
            "description": "Meta's own WhatsApp Flow id (what a send or template button names)."
          },
          "name": {
            "type": "string"
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "SIGN_UP",
                "SIGN_IN",
                "APPOINTMENT_BOOKING",
                "LEAD_GENERATION",
                "CONTACT_US",
                "CUSTOMER_SUPPORT",
                "SURVEY",
                "OTHER"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "PUBLISHED",
              "DEPRECATED",
              "BLOCKED",
              "THROTTLED"
            ],
            "description": "Meta lifecycle status, mirrored (sync, publish, webhooks)."
          },
          "authoring": {
            "type": "string",
            "enum": [
              "builder",
              "json"
            ],
            "description": "`builder`: `builder_state` is the source of truth (null = created outside Vitrina, read-only until converted). `json`: raw Flow JSON in `flow_json`."
          },
          "builder_state": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "screens": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "title": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80
                    },
                    "footer_label": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 35
                    },
                    "footer_captions": {
                      "type": "object",
                      "properties": {
                        "left": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 15
                        },
                        "center": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 15
                        },
                        "right": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 15
                        }
                      },
                      "additionalProperties": false
                    },
                    "next_rules": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "when": {
                            "$ref": "#/components/schemas/WhatsappFlowCondition"
                          },
                          "goto": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 9
                          }
                        },
                        "required": [
                          "when",
                          "goto"
                        ],
                        "additionalProperties": false
                      },
                      "maxItems": 20
                    },
                    "default_next": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 9
                    },
                    "components": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WhatsappFlowComponent"
                      },
                      "maxItems": 49
                    }
                  },
                  "required": [
                    "title",
                    "components"
                  ]
                },
                "minItems": 1,
                "maxItems": 10
              },
              "summary_screen": {
                "type": "boolean"
              }
            },
            "required": [
              "screens"
            ],
            "description": "Builder authoring: ordered screens of palette components."
          },
          "flow_json": {
            "type": [
              "string",
              "null"
            ],
            "description": "Raw-JSON authoring: the Flow JSON last uploaded to Meta (null for builder flows — read it with GET …/flow-json)."
          },
          "validation_errors": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "object",
              "additionalProperties": {}
            },
            "description": "Meta's findings on the last upload; non-empty blocks publish."
          },
          "preview_url": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cached Meta web preview URL."
          },
          "preview_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the cached preview URL expires."
          },
          "key": {
            "type": "string",
            "description": "Per-number slug skills and knowledge documents reference."
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the AI agent should send it."
          },
          "ai_enabled": {
            "type": "boolean",
            "description": "The AI agent may send it while PUBLISHED."
          },
          "message_body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default interactive-message text."
          },
          "message_cta": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default button label."
          },
          "message_header": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default header."
          },
          "message_footer": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default footer."
          },
          "completion_mode": {
            "type": "string",
            "enum": [
              "agent",
              "book_then_agent",
              "book_only",
              "automation_only"
            ],
            "description": "What a submitted form does: `agent` (the answers reach the AI agent), `book_then_agent` (books at submit, the agent confirms), `book_only` (books at submit + `confirmation_message`, no AI turn), `automation_only` (no AI turn; events, webhooks and automations only)."
          },
          "field_map": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Form field name → contact field (`name` | `email` | `document` | `phone` | `attr:<contact attribute key>`), applied on completion; a blank answer never clears a value."
          },
          "confirmation_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fixed confirmation of `book_only` / `automation_only` ({{field}} placeholders, {{cita}} = the booking); null = the default text."
          },
          "health_status": {
            "type": "string",
            "enum": [
              "healthy",
              "degraded",
              "throttled",
              "blocked"
            ],
            "description": "`blocked` / `throttled` from Meta’s status, `degraded` while an endpoint/version alert is open, else `healthy`."
          },
          "active_alerts": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/WhatsappFlowHealthAlert"
            },
            "description": "Open alerts keyed `error_rate:endpoint` / `error_rate:client` / `latency` / `availability` / `version_expiry` (+ `status` with Meta’s reason while throttled or blocked)."
          },
          "last_alert": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WhatsappFlowHealthAlert"
              },
              {
                "type": [
                  "object",
                  "null"
                ],
                "description": "The most recent health event of any kind (recoveries too)."
              }
            ]
          },
          "health_updated_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the last health event was applied."
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "messaging_account_id",
          "meta_flow_id",
          "name",
          "categories",
          "status",
          "authoring",
          "builder_state",
          "flow_json",
          "validation_errors",
          "preview_url",
          "preview_expires_at",
          "key",
          "description",
          "ai_enabled",
          "message_body",
          "message_cta",
          "message_header",
          "message_footer",
          "completion_mode",
          "field_map",
          "confirmation_message",
          "health_status",
          "active_alerts",
          "last_alert",
          "health_updated_at",
          "created_at",
          "updated_at"
        ],
        "description": "A WhatsApp Flow managed in Vitrina — authored in the builder, as raw Flow JSON, or synced from Meta."
      },
      "WhatsappFlowJsonInput": {
        "anyOf": [
          {
            "type": "string",
            "minLength": 2,
            "maxLength": 10000000
          },
          {
            "type": "object",
            "additionalProperties": {}
          }
        ],
        "description": "A raw Meta Flow JSON (https://developers.facebook.com/docs/whatsapp/flows/reference/flowjson) — a string kept byte-for-byte, or an object (serialized with 2-space indentation). Must be a JSON object with `version` and a non-empty `screens`; everything else is validated by Meta, whose findings return as `validation_errors`. A Flow JSON that declares `data_api_version` gets the workspace data endpoint as its `endpoint_uri`, but Vitrina's data route serves BUILDER flows only for now: a raw-JSON flow that calls it receives a clear error instead of data.",
        "example": {
          "version": "7.3",
          "screens": [
            {
              "id": "WELCOME",
              "title": "Agenda tu visita",
              "terminal": true,
              "layout": {
                "type": "SingleColumnLayout",
                "children": [
                  {
                    "type": "Footer",
                    "label": "Enviar",
                    "on-click-action": {
                      "name": "complete",
                      "payload": {}
                    }
                  }
                ]
              }
            }
          ]
        }
      },
      "WhatsappFlowComponentCapability": {
        "type": "object",
        "properties": {
          "kind": {
            "type": "string",
            "description": "`builder_state` component `kind`."
          },
          "metaType": {
            "type": "string",
            "description": "Flow JSON component it compiles to."
          },
          "minVersion": {
            "type": "string",
            "description": "Lowest Flow JSON version Meta documents it in."
          },
          "group": {
            "type": "string"
          },
          "value": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shape of its answer (null = collects nothing)."
          },
          "props": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "type": {
                  "type": "string"
                },
                "required": {
                  "type": "boolean"
                },
                "max": {
                  "type": "number"
                },
                "min": {
                  "type": "number"
                },
                "values": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "note": {
                  "type": "string"
                }
              },
              "required": [
                "type"
              ]
            }
          },
          "logic": {
            "type": "object",
            "properties": {
              "visibleWhen": {
                "type": "boolean"
              },
              "requiredWhen": {
                "type": "boolean"
              },
              "conditionOps": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "sensitive": {
                "type": "boolean"
              },
              "source": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "options",
                  "dates"
                ]
              },
              "prefill": {
                "type": "boolean"
              }
            },
            "required": [
              "visibleWhen",
              "requiredWhen",
              "conditionOps",
              "sensitive",
              "source",
              "prefill"
            ]
          },
          "limits": {
            "type": "object",
            "properties": {
              "perScreen": {
                "type": "number"
              },
              "perFlow": {
                "type": "number"
              },
              "aloneOnScreen": {
                "type": "boolean"
              },
              "notOnLastScreen": {
                "type": "boolean"
              },
              "note": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "kind",
          "metaType",
          "minVersion",
          "group",
          "value",
          "props",
          "logic",
          "limits"
        ]
      },
      "WhatsappFlowCapabilities": {
        "type": "object",
        "properties": {
          "messaging_account_id": {
            "type": "string",
            "format": "uuid"
          },
          "flow_json_version": {
            "type": "string",
            "description": "Flow JSON version the builder generates."
          },
          "data_api_version": {
            "type": "string",
            "description": "Data API version of live-data (endpoint) builder flows."
          },
          "authoring_modes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "builder",
                "json"
              ]
            }
          },
          "vertical": {
            "type": [
              "string",
              "null"
            ]
          },
          "subject_label": {
            "type": "string",
            "description": "What a conversation is about in this workspace’s vocabulary (vehicle, property, product, treatment, …)."
          },
          "booking_engine": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "key": {
                "type": "string"
              },
              "can_book": {
                "type": "boolean"
              },
              "booking_link_only": {
                "type": "boolean"
              }
            },
            "required": [
              "key",
              "can_book",
              "booking_link_only"
            ]
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WhatsappFlowComponentCapability"
            },
            "description": "The builder palette — the same description the generator and the Vitrina UI use (describeComponentCapabilities)."
          },
          "builder": {
            "type": "object",
            "properties": {
              "screen": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "max": {
                      "type": "number"
                    },
                    "min": {
                      "type": "number"
                    },
                    "values": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "type"
                  ]
                }
              },
              "state": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "type": {
                      "type": "string"
                    },
                    "required": {
                      "type": "boolean"
                    },
                    "max": {
                      "type": "number"
                    },
                    "min": {
                      "type": "number"
                    },
                    "values": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "note": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "type"
                  ]
                }
              },
              "condition": {
                "type": "object",
                "properties": {
                  "ops": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "note": {
                    "type": "string"
                  }
                },
                "required": [
                  "ops",
                  "note"
                ]
              },
              "prefill": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "fromPatterns": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "modes": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "note": {
                    "type": "string"
                  }
                },
                "required": [
                  "from",
                  "fromPatterns",
                  "modes",
                  "note"
                ]
              },
              "presets": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "label": {
                      "type": "string"
                    },
                    "country": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "checksum": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "id",
                    "label",
                    "country",
                    "checksum"
                  ]
                }
              }
            },
            "required": [
              "screen",
              "state",
              "condition",
              "prefill",
              "presets"
            ],
            "description": "Screen- and state-level props (branching, captions, summary screen), condition ops, conversation-context prefill and text presets (country-aware)."
          },
          "data_sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "source": {
                  "type": "string",
                  "description": "The value for a component’s `source.id` (`inventory` = the workspace’s catalog for its vertical; the legacy `vehicles` alias is accepted on existing flows but never offered)."
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "options",
                    "dates"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "params": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "required": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "name",
                      "required"
                    ]
                  }
                },
                "available": {
                  "type": "boolean"
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Why it is not available in this workspace (null when it is)."
                }
              },
              "required": [
                "source",
                "kind",
                "label",
                "description",
                "params",
                "available",
                "reason"
              ]
            }
          },
          "context_keys": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "source": {
                  "type": "string",
                  "enum": [
                    "conversation",
                    "contact",
                    "contact_attr",
                    "conversation_attr"
                  ]
                }
              },
              "required": [
                "key",
                "label",
                "source"
              ]
            },
            "description": "Keys a field can be prefilled from at send time: the generic ones plus this workspace’s custom contact/conversation attributes."
          },
          "completion_modes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "mode": {
                  "type": "string",
                  "enum": [
                    "agent",
                    "book_then_agent",
                    "book_only",
                    "automation_only"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "books_at_submit": {
                  "type": "boolean"
                },
                "available": {
                  "type": "boolean"
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Why it is not available in this workspace (null when it is)."
                }
              },
              "required": [
                "mode",
                "label",
                "description",
                "books_at_submit",
                "available",
                "reason"
              ]
            }
          },
          "limits": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            }
          }
        },
        "required": [
          "messaging_account_id",
          "flow_json_version",
          "data_api_version",
          "authoring_modes",
          "vertical",
          "subject_label",
          "booking_engine",
          "components",
          "builder",
          "data_sources",
          "context_keys",
          "completion_modes",
          "limits"
        ]
      },
      "WhatsappManagedFlowJson": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "authoring": {
            "type": "string",
            "enum": [
              "builder",
              "json"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "stored",
              "generated",
              "meta"
            ],
            "description": "`stored`: a json flow’s own Flow JSON; `generated`: the builder’s output (what the last save uploaded); `meta`: downloaded from Meta (a flow created outside Vitrina)."
          },
          "flow_json": {
            "type": "string",
            "description": "The Flow JSON, as a string."
          }
        },
        "required": [
          "id",
          "authoring",
          "source",
          "flow_json"
        ]
      },
      "BookingRosterProfessional": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "clinic_professional.id"
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The vendor's id — HealthAtom `profesional_id` for /healthatom/slots and POST /healthatom/citas; Reservo agenda id; null when native."
          },
          "nombre": {
            "type": "string"
          },
          "especialidad": {
            "type": [
              "string",
              "null"
            ]
          },
          "especialidades": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sucursal_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "HealthAtom sucursal ids the professional works at; empty when the engine does not say."
          },
          "intervalo_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "agenda_online": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "external_id",
          "nombre",
          "especialidad",
          "especialidades",
          "sucursal_ids",
          "intervalo_minutes",
          "agenda_online"
        ]
      },
      "BookingRosterService": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The id the engine's booking takes: a motivo's uuid (HealthAtom `servicio_id`), a HealthAtom prestación's vendor id, or the clinic_service uuid (Reservo / native)."
          },
          "kind": {
            "type": "string",
            "enum": [
              "motivo",
              "service"
            ]
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "nombre": {
            "type": "string"
          },
          "categoria": {
            "type": [
              "string",
              "null"
            ]
          },
          "especialidad": {
            "type": [
              "string",
              "null"
            ]
          },
          "duracion": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minutes, when known."
          },
          "profesionales": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Motivos: vendor ids of the professionals who attend it; empty = anyone."
          },
          "duraciones": {
            "type": "object",
            "additionalProperties": {
              "type": [
                "integer",
                "null"
              ]
            },
            "description": "Motivos: each professional's own length."
          }
        },
        "required": [
          "id",
          "kind",
          "external_id",
          "nombre",
          "categoria",
          "especialidad",
          "duracion",
          "profesionales",
          "duraciones"
        ]
      },
      "BookingRoster": {
        "type": "object",
        "properties": {
          "engine": {
            "type": "string",
            "enum": [
              "healthatom",
              "reservo",
              "native"
            ]
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the roster was last read from the vendor; null when native."
          },
          "professionals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingRosterProfessional"
            }
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingRosterService"
            }
          }
        },
        "required": [
          "engine",
          "synced_at",
          "professionals",
          "services"
        ]
      },
      "BookingRosterRefresh": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "done",
              "queued",
              "not_needed"
            ]
          },
          "roster": {
            "$ref": "#/components/schemas/BookingRoster"
          }
        },
        "required": [
          "status",
          "roster"
        ]
      },
      "ClinicServiceEntryFlagsResult": {
        "type": "object",
        "properties": {
          "updated": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "updated"
        ]
      },
      "ClinicMotivoProfessional": {
        "type": "object",
        "properties": {
          "external_id": {
            "type": "string"
          },
          "nombre": {
            "type": "string"
          },
          "vendor_duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "active": {
            "type": "boolean"
          }
        },
        "required": [
          "external_id",
          "nombre",
          "vendor_duration_minutes",
          "duration_minutes",
          "active"
        ]
      },
      "ClinicMotivo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "tenant_id": {
            "type": "string",
            "format": "uuid"
          },
          "source": {
            "type": "string",
            "enum": [
              "healthatom"
            ]
          },
          "external_id": {
            "type": "string"
          },
          "nombre": {
            "type": "string"
          },
          "especialidad": {
            "type": [
              "string",
              "null"
            ]
          },
          "vendor_duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "vendor_active": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean"
          },
          "agent_bookable": {
            "type": "boolean"
          },
          "color": {
            "type": [
              "string",
              "null"
            ]
          },
          "agenda_online": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "overrides": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "duration_minutes",
                "active",
                "professionals"
              ]
            }
          },
          "synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "professionals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicMotivoProfessional"
            }
          }
        },
        "required": [
          "id",
          "tenant_id",
          "source",
          "external_id",
          "nombre",
          "especialidad",
          "vendor_duration_minutes",
          "duration_minutes",
          "vendor_active",
          "active",
          "agent_bookable",
          "color",
          "agenda_online",
          "overrides",
          "synced_at",
          "created_at",
          "updated_at",
          "professionals"
        ]
      },
      "ClinicMotivoList": {
        "type": "object",
        "properties": {
          "booking_catalog": {
            "type": "string",
            "enum": [
              "motivos",
              "prestaciones"
            ]
          },
          "product": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "dentalink",
              "medilink"
            ]
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "motivos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClinicMotivo"
            }
          }
        },
        "required": [
          "booking_catalog",
          "product",
          "last_synced_at",
          "last_error",
          "motivos"
        ]
      },
      "ClinicMotivoResyncResult": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "minimum": 0
          },
          "synced_at": {
            "type": "string"
          },
          "added": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                },
                "especialidad": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "nombre",
                "especialidad"
              ]
            }
          },
          "updated": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                },
                "especialidad": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "fields": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              },
              "required": [
                "id",
                "nombre",
                "especialidad",
                "fields"
              ]
            }
          },
          "removed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                },
                "especialidad": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "id",
                "nombre",
                "especialidad"
              ]
            }
          },
          "kept": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "uuid"
                },
                "nombre": {
                  "type": "string"
                },
                "especialidad": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "field": {
                  "type": "string",
                  "enum": [
                    "duration_minutes",
                    "active",
                    "professionals"
                  ]
                },
                "vendor": {
                  "type": "string"
                },
                "vitrina": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "nombre",
                "especialidad",
                "field",
                "vendor",
                "vitrina"
              ]
            }
          }
        },
        "required": [
          "total",
          "synced_at",
          "added",
          "updated",
          "removed",
          "kept"
        ]
      },
      "ClinicServiceAgentBookableCount": {
        "type": "object",
        "properties": {
          "bookable": {
            "type": "integer",
            "minimum": 0
          },
          "reception_only": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        },
        "required": [
          "bookable",
          "reception_only",
          "total"
        ]
      },
      "ClinicServiceMatchFilter": {
        "type": "object",
        "properties": {
          "search": {
            "type": "string",
            "minLength": 2,
            "maxLength": 120
          },
          "active": {
            "type": "string",
            "enum": [
              "true",
              "false"
            ]
          },
          "review_state": {
            "type": "string",
            "enum": [
              "pending",
              "stale",
              "draft",
              "reviewed"
            ]
          },
          "is_entry": {
            "type": "string",
            "enum": [
              "true",
              "false",
              "unclassified"
            ]
          },
          "arancel": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "maxItems": 1000
          },
          "categoria": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "agent_bookable": {
            "type": "string",
            "enum": [
              "yes",
              "no"
            ]
          }
        }
      }
    },
    "parameters": {}
  },
  "paths": {
    "/ai-agents": {
      "get": {
        "summary": "List AI agents",
        "description": "Every agent in the workspace with the stages wired to it, plus the summary the list cards render: `live_version` (the highest published `ai_agent_version`, `null` when never published), `has_draft` (any staged change waiting to be published), and `counts` { tools, kb_files, skills } — enabled tool wirings and NON-DELETED attachments. `live_version: null` with `has_draft: true` is an agent that has been configured but never went live.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "AI agents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Asistente de ventas",
                      "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                      "status": "active",
                      "is_default": true,
                      "temperature": null,
                      "default_max_steps": 5,
                      "autonomy_level": "supervised",
                      "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                      "knowledge_tags": [
                        "horarios",
                        "garantia"
                      ],
                      "tags": [],
                      "followups_enabled": true,
                      "allowed_url_prefixes": [],
                      "published_version_id": "a3a3a3a3-1000-4000-8000-000000000007",
                      "draft_system_prompt": null,
                      "draft_temperature": null,
                      "draft_max_steps": null,
                      "draft_tool_wiring": null,
                      "draft_skill_ids": null,
                      "draft_kb_file_ids": null,
                      "draft_knowledge_tags": null,
                      "draft_updated_at": null,
                      "created_at": "2026-08-04T19:07:05.226Z",
                      "updated_at": "2026-09-17T14:49:02.893Z",
                      "live_version": 7,
                      "has_draft": false,
                      "counts": {
                        "tools": 15,
                        "kb_files": 2,
                        "skills": 1
                      }
                    },
                    {
                      "id": "a3a3a3a3-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Asistente de postventa",
                      "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                      "status": "active",
                      "is_default": false,
                      "temperature": null,
                      "default_max_steps": 5,
                      "autonomy_level": "supervised",
                      "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                      "knowledge_tags": [
                        "horarios",
                        "garantia"
                      ],
                      "tags": [],
                      "followups_enabled": true,
                      "allowed_url_prefixes": [],
                      "published_version_id": "a3a3a3a3-1000-4000-8000-000000000007",
                      "draft_system_prompt": null,
                      "draft_temperature": null,
                      "draft_max_steps": null,
                      "draft_tool_wiring": null,
                      "draft_skill_ids": null,
                      "draft_kb_file_ids": null,
                      "draft_knowledge_tags": null,
                      "draft_updated_at": null,
                      "created_at": "2026-08-04T19:07:05.226Z",
                      "updated_at": "2026-09-17T14:49:02.893Z",
                      "live_version": null,
                      "has_draft": true,
                      "counts": {
                        "tools": 3,
                        "kb_files": 0,
                        "skills": 0
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create AI agent",
        "description": "Only `name` is required. Vitrina picks and manages the model an agent runs on; it is not part of the API. The agent is born with an empty `system_prompt` and no `published_version_id` — it does nothing until a draft is saved and published. Answers 201.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000,
                    "default": ""
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Asistente de postventa",
                "description": "Coordina servicio técnico y repuestos."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "AI agent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de postventa",
                    "description": "Coordina servicio técnico y repuestos.",
                    "status": "active",
                    "is_default": false,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "",
                    "knowledge_tags": [],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": null,
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ai-agents/{id}": {
      "get": {
        "summary": "Get AI agent",
        "description": "The full row: live config plus every staged `draft_*` field, plus the read-only `comment_dm_criteria_default_preview` (a Spanish summary of the comment→DM criterion used when `comment_dm_criteria` is empty, per the workspace vertical).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "AI agent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas",
                    "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                    "status": "active",
                    "is_default": true,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "knowledge_tags": [
                      "horarios",
                      "garantia"
                    ],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": "a3a3a3a3-1000-4000-8000-000000000007",
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z",
                    "comment_dm_criteria_default_preview": "Escríbele a quien muestre interés de compra: pregunta por precio, financiamiento, disponibilidad o quiere agendar una visita, y a quien pregunte por un vehículo del catálogo. Ignora felicitaciones, quejas y comentarios de otros vendedores."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update AI agent",
        "description": "Every field optional. Unlike the system prompt and the tool/skill/KB wiring, `name`/`description`/`followups_enabled`/`allowed_url_prefixes` are LIVE IMMEDIATELY — they do not stage on the draft and do not need a publish.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "followups_enabled": {
                    "type": "boolean"
                  },
                  "allowed_url_prefixes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 300
                    },
                    "maxItems": 50
                  }
                }
              },
              "example": {
                "name": "Asistente de postventa (CL)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI agent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de postventa (CL)",
                    "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                    "status": "active",
                    "is_default": true,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "knowledge_tags": [
                      "horarios",
                      "garantia"
                    ],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": "a3a3a3a3-1000-4000-8000-000000000007",
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete AI agent",
        "description": "Removes the agent. Its published versions, skills and KB attachments do not follow it — skills and KB files are tenant-shared resources that survive independently. There is no undelete; recreate and reconfigure from scratch. 204.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "x-vitrina-connector-excluded": true
      }
    },
    "/ai-agents/{id}/system-prompt": {
      "get": {
        "summary": "Get system prompt",
        "description": "The LIVE `system_prompt` — what the runtime reads on the next turn. The staged edit, if any, is on `draft_system_prompt` (`GET /ai-agents/{id}/draft`). `config_version` (also the `X-Config-Version` header) is the live revision to send back as `expected_version`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "System prompt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update the LIVE system prompt",
        "description": "Writes the LIVE `system_prompt` directly — the next turn uses it, with no draft and no publish step (an `ai_agent_version` snapshot is still recorded). To stage a change for review use `PUT /ai-agents/{id}/draft` then `POST /ai-agents/{id}/publish`. `expected_version` here is the live `config_version` (`GET /ai-agents/{id}/system-prompt`). Writing the live prompt while a draft is staged makes that draft stale: its publish then answers 409 `draft_base_stale`.\n\n**Optimistic concurrency.** The response carries the resource version in `X-Config-Version` (and `ETag`). Send it back on the next write as `expected_version` (or `If-Match`); a stale value answers **409** `VERSION_CONFLICT` with `error.conflict` = `{resource, reason, expected_version, current_version, changed_fields, current_updated_at, read_path, hint}` and writes nothing. Omitting it still works (logged).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "system_prompt": {
                    "type": "string"
                  },
                  "knowledge_tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "expected_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The live `config_version` version you last read (the `X-Config-Version` response header, or the version field in the body). If it is no longer current the write is refused with 409 `VERSION_CONFLICT` and nothing is written; the error carries the current version and a diff hint. Optional for backward compatibility (an omitted value is accepted and logged), but send it: without it two editors silently overwrite each other. The `If-Match` header is accepted as an alternative."
                  }
                },
                "required": [
                  "system_prompt"
                ]
              },
              "example": {
                "system_prompt": "Eres un asistente de ventas. Responde de forma breve, cercana y en español."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "System prompt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/tools": {
      "get": {
        "summary": "List tools registered to this AI agent",
        "description": "The LIVE tool wiring — platform tools and custom tools currently enabled for this agent. `PUT /ai-agents/{id}/tools` replaces this set wholesale.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a3a3a3a3-2000-4000-8000-000000000001",
                      "name": "search_vehicle_stock",
                      "kind": "builtin",
                      "external_name": "Search vehicle stock",
                      "description": "Search the dealer's current vehicle stock."
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the agent’s tools",
        "description": "Replaces the whole wiring with `tool_names` — this is a set, not a patch, so omitting a currently-wired tool unwires it.\n\n**Applies LIVE, not to the draft.** That is deliberate and was a bug fix: staging tool selection meant Save wrote a draft the picker never read back, so the selection appeared to revert on refetch. The runtime and the picker now read the same live set, and a save here changes what the agent can do on its very next turn without a publish.\n\nNames come from `/ai-agents/tools-catalog`. A name the workspace is not entitled to is DROPPED rather than rejected, so compare `meta.total` against what you sent — a 200 does not mean everything was wired. Removal is narrower than that on purpose: only a tool you left OUT of `tool_names` is unwired. One you still selected that happens to be gated right now (a calendar that momentarily looks disconnected) keeps its link, because the runtime already hides it and deleting the wiring would lose it permanently once the integration came back.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tool_names": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 40
                  }
                },
                "required": [
                  "tool_names"
                ]
              },
              "example": {
                "tool_names": [
                  "search_vehicle_stock",
                  "book_appointment"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tools actually wired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "name": "search_vehicle_stock",
                      "wired": true
                    },
                    {
                      "name": "book_appointment",
                      "wired": true
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/clone": {
      "post": {
        "summary": "Clone an AI agent (copies tool links, drops is_default)",
        "description": "Copies the SOURCE agent’s live config plus its tool wirings into a brand-new agent — skills and KB attachments included, each by reference (the shared row, not a duplicate). The clone is never `is_default` and starts with no `published_version_id`: publish it explicitly once you are happy with it. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                }
              },
              "example": {
                "name": "Asistente de ventas (copia)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Cloned AI agent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000003",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas (copia)",
                    "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                    "status": "active",
                    "is_default": false,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "knowledge_tags": [
                      "horarios",
                      "garantia"
                    ],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": "a3a3a3a3-1000-4000-8000-000000000007",
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/versions": {
      "get": {
        "summary": "List version history for an AI agent",
        "description": "Newest first. Each row is the FULL stored version row EXCEPT that `skill_snapshot[].content` is omitted — the timeline never renders a skill body and it is the only unbounded field on the row. Open a version to get the bodies. The per-version \"+N skills / +N archivos / +N herramientas\" chips are a client-side diff of the id arrays against the previous row (ADR 0093).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "ai_agent_id": {
                            "type": "string"
                          },
                          "version_number": {
                            "type": "number"
                          },
                          "label": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "system_prompt": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "voice_instructions": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "comment_dm_criteria": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "knowledge_tags": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "function_ids": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "temperature": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "default_max_steps": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "tool_snapshot": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "function_id": {
                                  "type": "string"
                                },
                                "enabled": {
                                  "type": "boolean"
                                },
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "name_override": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "description_override": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "tool_id": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "function_id",
                                "enabled",
                                "name_override",
                                "description_override",
                                "tool_id"
                              ]
                            }
                          },
                          "skill_ids": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "kb_file_ids": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "skill_snapshot": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "description": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "channel_overrides": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "additionalProperties": {
                                    "type": "string"
                                  }
                                },
                                "updated_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "id",
                                "name",
                                "description",
                                "channel_overrides",
                                "updated_at"
                              ]
                            }
                          },
                          "kb_snapshot": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "content_type": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "size_bytes": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "s3_key": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "ingested_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "id",
                                "name",
                                "content_type",
                                "size_bytes",
                                "s3_key",
                                "ingested_at"
                              ]
                            }
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "published_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "ai_agent_id",
                          "version_number",
                          "label",
                          "system_prompt",
                          "voice_instructions",
                          "comment_dm_criteria",
                          "knowledge_tags",
                          "function_ids",
                          "temperature",
                          "default_max_steps",
                          "tool_snapshot",
                          "skill_ids",
                          "kb_file_ids",
                          "skill_snapshot",
                          "kb_snapshot",
                          "created_by",
                          "published_by",
                          "created_at"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "a3a3a3a3-1000-4000-8000-000000000007",
                      "version_number": 7,
                      "label": "Published draft",
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-17T14:49:02.601Z"
                    },
                    {
                      "id": "a3a3a3a3-1000-4000-8000-000000000006",
                      "version_number": 6,
                      "label": "Rollback to v4",
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-10T11:02:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/versions/{version}": {
      "get": {
        "summary": "Get a specific stored version",
        "description": "The full row (skill bodies included) plus `resolved` — ADR 0093’s answer to \"what became of the things this version referenced\". Skills and KB files are tenant-shared resources referenced BY ID, so each one reports a state: `current`, `modified` (the skill exists but its content or channel_overrides drifted since this version), `deleted` (soft-deleted — a restore brings it back) or `missing` (no row at all; a restore drops it). Names for deleted/missing items come from the version’s own snapshot.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The version, with its references resolved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "ai_agent_id": {
                          "type": "string"
                        },
                        "version_number": {
                          "type": "number"
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "system_prompt": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voice_instructions": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "comment_dm_criteria": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "knowledge_tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "function_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "temperature": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "default_max_steps": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "tool_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "function_id": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "name_override": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "description_override": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "tool_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "function_id",
                              "enabled",
                              "name_override",
                              "description_override",
                              "tool_id"
                            ]
                          }
                        },
                        "skill_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "kb_file_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "skill_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "description": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "content": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "channel_overrides": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "updated_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "description",
                              "content",
                              "channel_overrides",
                              "updated_at"
                            ]
                          }
                        },
                        "kb_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "content_type": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "size_bytes": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "s3_key": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "ingested_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "content_type",
                              "size_bytes",
                              "s3_key",
                              "ingested_at"
                            ]
                          }
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "published_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "resolved": {
                          "type": "object",
                          "properties": {
                            "tools": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "function_id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "enabled": {
                                    "type": "boolean"
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "current",
                                      "missing"
                                    ]
                                  }
                                },
                                "required": [
                                  "function_id",
                                  "name",
                                  "enabled",
                                  "state"
                                ]
                              }
                            },
                            "skills": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "description": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "content": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "voice": {
                                    "type": "boolean"
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "current",
                                      "modified",
                                      "deleted",
                                      "missing"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "name",
                                  "description",
                                  "content",
                                  "voice",
                                  "state"
                                ]
                              }
                            },
                            "kb_files": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "content_type": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "size_bytes": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "current",
                                      "deleted",
                                      "missing"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "name",
                                  "content_type",
                                  "size_bytes",
                                  "state"
                                ]
                              }
                            }
                          },
                          "required": [
                            "tools",
                            "skills",
                            "kb_files"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "ai_agent_id",
                        "version_number",
                        "label",
                        "system_prompt",
                        "voice_instructions",
                        "comment_dm_criteria",
                        "knowledge_tags",
                        "function_ids",
                        "temperature",
                        "default_max_steps",
                        "tool_snapshot",
                        "skill_ids",
                        "kb_file_ids",
                        "skill_snapshot",
                        "kb_snapshot",
                        "created_by",
                        "published_by",
                        "created_at",
                        "resolved"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-1000-4000-8000-000000000007",
                    "version_number": 7,
                    "label": "Published draft",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "skill_snapshot": [
                      {
                        "id": "a4a4a4a4-0000-4000-8000-000000000001",
                        "name": "Agendar hora",
                        "state": "current"
                      }
                    ],
                    "kb_file_snapshot": [
                      {
                        "id": "a6a6a6a6-0000-4000-8000-000000000001",
                        "name": "Garantías 2026.pdf",
                        "state": "current"
                      }
                    ],
                    "resolved": {
                      "skills": [
                        {
                          "id": "a4a4a4a4-0000-4000-8000-000000000001",
                          "state": "current"
                        }
                      ],
                      "kb_files": [
                        {
                          "id": "a6a6a6a6-0000-4000-8000-000000000001",
                          "state": "current"
                        }
                      ]
                    },
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-17T14:49:02.601Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/versions/{version}/restore": {
      "post": {
        "summary": "Restore an AI agent to a stored version",
        "description": "Loads the version into the DRAFT (nothing goes live until Publish). ADR 0093 — restore also REPAIRS the references first: a soft-deleted skill is un-deleted, a soft-deleted KB file is un-deleted and re-ingested from S3, and a reference with no row left is dropped from the staged draft rather than staged into a publish that would FK-fail. `restore.recovered` lists what came back, `restore.warnings` what was dropped.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The restored version plus what the restore repaired",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "ai_agent_id": {
                          "type": "string"
                        },
                        "version_number": {
                          "type": "number"
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "system_prompt": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voice_instructions": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "comment_dm_criteria": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "knowledge_tags": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "function_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "temperature": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "default_max_steps": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "tool_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "function_id": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "name_override": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "description_override": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "tool_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "function_id",
                              "enabled",
                              "name_override",
                              "description_override",
                              "tool_id"
                            ]
                          }
                        },
                        "skill_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "kb_file_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "skill_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "description": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "content": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "channel_overrides": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "updated_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "description",
                              "content",
                              "channel_overrides",
                              "updated_at"
                            ]
                          }
                        },
                        "kb_snapshot": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "content_type": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "size_bytes": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "s3_key": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "ingested_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "content_type",
                              "size_bytes",
                              "s3_key",
                              "ingested_at"
                            ]
                          }
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "published_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "restore": {
                          "type": "object",
                          "properties": {
                            "recovered": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "skill",
                                      "kb_file"
                                    ]
                                  },
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "kind",
                                  "id",
                                  "name"
                                ]
                              }
                            },
                            "warnings": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "tool",
                                      "skill",
                                      "kb_file"
                                    ]
                                  },
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason": {
                                    "type": "string",
                                    "enum": [
                                      "missing"
                                    ]
                                  }
                                },
                                "required": [
                                  "kind",
                                  "id",
                                  "name",
                                  "reason"
                                ]
                              }
                            }
                          },
                          "required": [
                            "recovered",
                            "warnings"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "ai_agent_id",
                        "version_number",
                        "label",
                        "system_prompt",
                        "voice_instructions",
                        "comment_dm_criteria",
                        "knowledge_tags",
                        "function_ids",
                        "temperature",
                        "default_max_steps",
                        "tool_snapshot",
                        "skill_ids",
                        "kb_file_ids",
                        "skill_snapshot",
                        "kb_snapshot",
                        "created_by",
                        "published_by",
                        "created_at",
                        "restore"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "restored": {
                      "version_number": 4
                    },
                    "restore": {
                      "recovered": [
                        {
                          "kind": "skill",
                          "id": "a4a4a4a4-0000-4000-8000-000000000001"
                        }
                      ],
                      "warnings": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/draft": {
      "get": {
        "summary": "Read the pending draft (system_prompt + knowledge_tags)",
        "description": "Only the staged `draft_*` fields, not the whole agent — `GET /ai-agents/{id}` already carries them alongside the live ones. `draft_updated_at: null` means there is no pending draft. `draft_version` (also `X-Config-Version`) is what `PUT /ai-agents/{id}/draft` takes as `expected_version` and publish as `expected_draft_version`; `draft_base_version` is the live `config_version` the draft was started on — when it differs from `config_version`, someone changed the live agent after this draft began.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "draft_system_prompt": "Eres un asistente de ventas. Ahora también ofreces financiamiento.",
                    "draft_knowledge_tags": [
                      "horarios",
                      "garantia",
                      "financiamiento"
                    ],
                    "draft_updated_at": "2026-09-21T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Edit the pending draft (does not affect live runtime)",
        "description": "The full-config draft (ADR 0010/0012): `system_prompt`, `voice_instructions`, `temperature`/`max_steps`, `tool_wiring`, `skill_ids`, `kb_file_ids`, `knowledge_tags`, `behavior_policies` (a partial patch merged over the staged policies). Every field optional and merged — an omitted field leaves the previous draft value untouched (an empty string IS a value: it stages a clear). Nothing here reaches the runtime until `POST /ai-agents/{id}/publish`. `expected_version` is the `draft_version` you read.\n\n**Optimistic concurrency.** The response carries the resource version in `X-Config-Version` (and `ETag`). Send it back on the next write as `expected_version` (or `If-Match`); a stale value answers **409** `VERSION_CONFLICT` with `error.conflict` = `{resource, reason, expected_version, current_version, changed_fields, current_updated_at, read_path, hint}` and writes nothing. Omitting it still works (logged).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "system_prompt": {
                    "type": "string"
                  },
                  "voice_instructions": {
                    "type": "string",
                    "maxLength": 40000
                  },
                  "comment_dm_criteria": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "knowledge_tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    }
                  },
                  "temperature": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 2
                  },
                  "max_steps": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 20
                  },
                  "tool_wiring": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "function_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "enabled": {
                          "type": "boolean",
                          "default": true
                        },
                        "name_override": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 120
                        },
                        "description_override": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 4000
                        },
                        "tool_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "function_id"
                      ]
                    },
                    "maxItems": 60
                  },
                  "skill_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  },
                  "kb_file_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 500
                  },
                  "behavior_policies": {
                    "type": "object",
                    "properties": {
                      "treatment_price": {
                        "type": "string",
                        "enum": [
                          "unset",
                          "first_ask",
                          "never"
                        ],
                        "description": "«Precio de tratamientos» (healthcare). `unset` (default): the agent follows its own instructions, nothing is added. `first_ask`: gives a treatment's price at the first price question — a fixed price, or the range anchored on the evaluation — then a concrete slot. `never`: the evaluation's price only."
                      },
                      "far_slot_waitlist": {
                        "type": "boolean",
                        "description": "«Ofrecer lista de espera si la primera hora está a más de 7 días» (#3314). Default false."
                      },
                      "urgency_slots": {
                        "type": "boolean",
                        "description": "«Dolor: ofrecer las primeras horas de hoy o mañana» (D72). A patient in pain is offered today's / tomorrow's first slots in the same turn; a handoff becomes a heads-up to reception, not the answer. Default false; true for the agent a new clinic's onboarding creates."
                      },
                      "specialist_fallback": {
                        "type": "boolean",
                        "description": "«Especialista a más de 7 días: ofrecer también una evaluación general esta semana» (D74). `find_slots` then also reports the clinic's general evaluation slots of the next 7 days. Default false."
                      },
                      "silent_handoff": {
                        "type": "boolean",
                        "description": "«Handoff silencioso» (#6016). Default false: a handoff always tells the customer a person takes over (a fixed line is added when the model wrote none). true: the handoff turn may end with no customer message, or with the model's own last line; no fixed handoff line is added. Assignment, alerts, team notification and the AI's silence after the handoff are unchanged."
                      },
                      "voice_note": {
                        "type": "object",
                        "properties": {
                          "enabled": {
                            "type": "boolean"
                          },
                          "voice_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "pattern": "^[A-Za-z0-9]{10,40}$"
                          },
                          "voice_settings": {
                            "type": "object",
                            "properties": {
                              "stability": {
                                "type": "number",
                                "minimum": 0,
                                "maximum": 1,
                                "description": "0..1. Higher = steadier, less expressive."
                              },
                              "similarity_boost": {
                                "type": "number",
                                "minimum": 0,
                                "maximum": 1
                              },
                              "speed": {
                                "type": "number",
                                "minimum": 0.7,
                                "maximum": 1.2,
                                "description": "0.7..1.2; 1 = normal pace."
                              },
                              "style": {
                                "type": "number",
                                "minimum": 0,
                                "maximum": 1
                              },
                              "use_speaker_boost": {
                                "type": "boolean"
                              }
                            },
                            "additionalProperties": false
                          },
                          "lexicon": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "from": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 60,
                                  "description": "Text as written («GB», «iPhone»)."
                                },
                                "alias": {
                                  "type": "string",
                                  "maxLength": 120,
                                  "description": "What the voice says instead («gigas», «aifón»)."
                                }
                              },
                              "required": [
                                "from",
                                "alias"
                              ],
                              "additionalProperties": false
                            },
                            "maxItems": 100
                          }
                        },
                        "additionalProperties": false
                      },
                      "opening_marks": {
                        "type": "string",
                        "enum": [
                          "inherit",
                          "none",
                          "keep"
                        ],
                        "description": "«Signos de apertura» for this agent. `inherit` (default): the workspace's `voice.opening_marks`. `none`: no opening «¿» «¡» (removed at send time). `keep`: the agent writes them."
                      },
                      "register": {
                        "type": "string",
                        "enum": [
                          "inherit",
                          "tu",
                          "usted",
                          "mirror"
                        ],
                        "description": "«Trato» for this agent. `inherit` (default): the workspace's `voice.agent_register`. `tu`, `usted`, or `mirror` (tú until the customer uses usted); wins over the agent's instructions."
                      }
                    },
                    "additionalProperties": false
                  },
                  "expected_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The `draft_version` version you last read (the `X-Config-Version` response header, or the version field in the body). If it is no longer current the write is refused with 409 `VERSION_CONFLICT` and nothing is written; the error carries the current version and a diff hint. Optional for backward compatibility (an omitted value is accepted and logged), but send it: without it two editors silently overwrite each other. The `If-Match` header is accepted as an alternative."
                  }
                }
              },
              "example": {
                "system_prompt": "Eres un asistente de ventas. Ahora también ofreces financiamiento.",
                "knowledge_tags": [
                  "horarios",
                  "garantia",
                  "financiamiento"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "draft_system_prompt": "Eres un asistente de ventas. Ahora también ofreces financiamiento.",
                    "draft_knowledge_tags": [
                      "horarios",
                      "garantia",
                      "financiamiento"
                    ],
                    "draft_updated_at": "2026-09-21T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard the pending draft",
        "description": "Clears every staged `draft_*` field. The live runtime is untouched — this only throws away work in progress. A no-op (still 204) when there is no pending draft.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/behavior-policies": {
      "get": {
        "summary": "Read the agent’s behaviour policies",
        "description": "Per-agent switches that change what the agent says (#3312/#3314): `treatment_price` (`unset` | `first_ask` | `never`, healthcare) and `far_slot_waitlist`, `urgency_slots` (D72), `specialist_fallback` (D74) and `silent_handoff` (#6016, «Handoff silencioso»: a handoff sends no announcement line; default false) and `voice_note` (#6021, «Voz para notas de voz»: `enabled` (default false), `voice_id` (ElevenLabs), `voice_settings` (`stability`, `similarity_boost`, `speed`, `style`, `use_speaker_boost`) and `lexicon` (`[{from, alias}]`); hear it with `POST /ai-agents/{id}/voice-note/preview`). `live` is what runs today, `draft` what is staged (null = nothing staged). Every default is the agent’s behaviour before policies existed: nothing is added to its prompt while a policy is unset. `price_policy_conflicts` lists lines of the agent’s own instructions (draft when staged) and active skills that already state a price rule, so a client can reconcile them before publishing. Change them with `PUT /ai-agents/{id}/draft` (`behavior_policies`) and `POST /ai-agents/{id}/publish`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Behaviour policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "live": {
                          "type": "object",
                          "properties": {
                            "treatment_price": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ],
                              "description": "«Precio de tratamientos» (healthcare). `unset` (default): the agent follows its own instructions, nothing is added. `first_ask`: gives a treatment's price at the first price question — a fixed price, or the range anchored on the evaluation — then a concrete slot. `never`: the evaluation's price only."
                            },
                            "far_slot_waitlist": {
                              "type": "boolean",
                              "description": "«Ofrecer lista de espera si la primera hora está a más de 7 días» (#3314). Default false."
                            },
                            "urgency_slots": {
                              "type": "boolean",
                              "description": "«Dolor: ofrecer las primeras horas de hoy o mañana» (D72). A patient in pain is offered today's / tomorrow's first slots in the same turn; a handoff becomes a heads-up to reception, not the answer. Default false; true for the agent a new clinic's onboarding creates."
                            },
                            "specialist_fallback": {
                              "type": "boolean",
                              "description": "«Especialista a más de 7 días: ofrecer también una evaluación general esta semana» (D74). `find_slots` then also reports the clinic's general evaluation slots of the next 7 days. Default false."
                            },
                            "silent_handoff": {
                              "type": "boolean",
                              "description": "«Handoff silencioso» (#6016). Default false: a handoff always tells the customer a person takes over (a fixed line is added when the model wrote none). true: the handoff turn may end with no customer message, or with the model's own last line; no fixed handoff line is added. Assignment, alerts, team notification and the AI's silence after the handoff are unchanged."
                            },
                            "voice_note": {
                              "type": "object",
                              "properties": {
                                "enabled": {
                                  "type": "boolean",
                                  "description": "«Voz para notas de voz». Default false. On, the agent may answer with a synthesized voice note (it is checked with Whisper first; on any failure the caller sends text)."
                                },
                                "voice_id": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "pattern": "^[A-Za-z0-9]{10,40}$",
                                  "description": "ElevenLabs voice id (cloned or stock). Null until chosen; enabled without it speaks nothing."
                                },
                                "voice_settings": {
                                  "type": "object",
                                  "properties": {
                                    "stability": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1,
                                      "description": "0..1. Higher = steadier, less expressive."
                                    },
                                    "similarity_boost": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1
                                    },
                                    "speed": {
                                      "type": "number",
                                      "minimum": 0.7,
                                      "maximum": 1.2,
                                      "description": "0.7..1.2; 1 = normal pace."
                                    },
                                    "style": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1
                                    },
                                    "use_speaker_boost": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "stability",
                                    "similarity_boost",
                                    "speed",
                                    "style",
                                    "use_speaker_boost"
                                  ],
                                  "additionalProperties": false
                                },
                                "lexicon": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "from": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 60,
                                        "description": "Text as written («GB», «iPhone»)."
                                      },
                                      "alias": {
                                        "type": "string",
                                        "maxLength": 120,
                                        "description": "What the voice says instead («gigas», «aifón»)."
                                      }
                                    },
                                    "required": [
                                      "from",
                                      "alias"
                                    ],
                                    "additionalProperties": false
                                  },
                                  "maxItems": 100,
                                  "description": "Alias rules applied after number normalization; the built-in GB/TB/hrs/$/%/N° rules always apply."
                                }
                              },
                              "required": [
                                "enabled",
                                "voice_id",
                                "voice_settings",
                                "lexicon"
                              ],
                              "additionalProperties": false,
                              "description": "«Voz para notas de voz» (#6021): the ElevenLabs voice the agent's synthesized voice notes use, with its lexicon. Off by default. Read always returns every field."
                            },
                            "opening_marks": {
                              "type": "string",
                              "enum": [
                                "inherit",
                                "none",
                                "keep"
                              ],
                              "description": "«Signos de apertura» for this agent. `inherit` (default): the workspace's `voice.opening_marks`. `none`: no opening «¿» «¡» (removed at send time). `keep`: the agent writes them."
                            },
                            "register": {
                              "type": "string",
                              "enum": [
                                "inherit",
                                "tu",
                                "usted",
                                "mirror"
                              ],
                              "description": "«Trato» for this agent. `inherit` (default): the workspace's `voice.agent_register`. `tu`, `usted`, or `mirror` (tú until the customer uses usted); wins over the agent's instructions."
                            }
                          },
                          "required": [
                            "treatment_price",
                            "far_slot_waitlist",
                            "urgency_slots",
                            "specialist_fallback",
                            "silent_handoff"
                          ],
                          "additionalProperties": false
                        },
                        "draft": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "treatment_price": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ],
                              "description": "«Precio de tratamientos» (healthcare). `unset` (default): the agent follows its own instructions, nothing is added. `first_ask`: gives a treatment's price at the first price question — a fixed price, or the range anchored on the evaluation — then a concrete slot. `never`: the evaluation's price only."
                            },
                            "far_slot_waitlist": {
                              "type": "boolean",
                              "description": "«Ofrecer lista de espera si la primera hora está a más de 7 días» (#3314). Default false."
                            },
                            "urgency_slots": {
                              "type": "boolean",
                              "description": "«Dolor: ofrecer las primeras horas de hoy o mañana» (D72). A patient in pain is offered today's / tomorrow's first slots in the same turn; a handoff becomes a heads-up to reception, not the answer. Default false; true for the agent a new clinic's onboarding creates."
                            },
                            "specialist_fallback": {
                              "type": "boolean",
                              "description": "«Especialista a más de 7 días: ofrecer también una evaluación general esta semana» (D74). `find_slots` then also reports the clinic's general evaluation slots of the next 7 days. Default false."
                            },
                            "silent_handoff": {
                              "type": "boolean",
                              "description": "«Handoff silencioso» (#6016). Default false: a handoff always tells the customer a person takes over (a fixed line is added when the model wrote none). true: the handoff turn may end with no customer message, or with the model's own last line; no fixed handoff line is added. Assignment, alerts, team notification and the AI's silence after the handoff are unchanged."
                            },
                            "voice_note": {
                              "type": "object",
                              "properties": {
                                "enabled": {
                                  "type": "boolean",
                                  "description": "«Voz para notas de voz». Default false. On, the agent may answer with a synthesized voice note (it is checked with Whisper first; on any failure the caller sends text)."
                                },
                                "voice_id": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "pattern": "^[A-Za-z0-9]{10,40}$",
                                  "description": "ElevenLabs voice id (cloned or stock). Null until chosen; enabled without it speaks nothing."
                                },
                                "voice_settings": {
                                  "type": "object",
                                  "properties": {
                                    "stability": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1,
                                      "description": "0..1. Higher = steadier, less expressive."
                                    },
                                    "similarity_boost": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1
                                    },
                                    "speed": {
                                      "type": "number",
                                      "minimum": 0.7,
                                      "maximum": 1.2,
                                      "description": "0.7..1.2; 1 = normal pace."
                                    },
                                    "style": {
                                      "type": "number",
                                      "minimum": 0,
                                      "maximum": 1
                                    },
                                    "use_speaker_boost": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "stability",
                                    "similarity_boost",
                                    "speed",
                                    "style",
                                    "use_speaker_boost"
                                  ],
                                  "additionalProperties": false
                                },
                                "lexicon": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "from": {
                                        "type": "string",
                                        "minLength": 1,
                                        "maxLength": 60,
                                        "description": "Text as written («GB», «iPhone»)."
                                      },
                                      "alias": {
                                        "type": "string",
                                        "maxLength": 120,
                                        "description": "What the voice says instead («gigas», «aifón»)."
                                      }
                                    },
                                    "required": [
                                      "from",
                                      "alias"
                                    ],
                                    "additionalProperties": false
                                  },
                                  "maxItems": 100,
                                  "description": "Alias rules applied after number normalization; the built-in GB/TB/hrs/$/%/N° rules always apply."
                                }
                              },
                              "required": [
                                "enabled",
                                "voice_id",
                                "voice_settings",
                                "lexicon"
                              ],
                              "additionalProperties": false,
                              "description": "«Voz para notas de voz» (#6021): the ElevenLabs voice the agent's synthesized voice notes use, with its lexicon. Off by default. Read always returns every field."
                            },
                            "opening_marks": {
                              "type": "string",
                              "enum": [
                                "inherit",
                                "none",
                                "keep"
                              ],
                              "description": "«Signos de apertura» for this agent. `inherit` (default): the workspace's `voice.opening_marks`. `none`: no opening «¿» «¡» (removed at send time). `keep`: the agent writes them."
                            },
                            "register": {
                              "type": "string",
                              "enum": [
                                "inherit",
                                "tu",
                                "usted",
                                "mirror"
                              ],
                              "description": "«Trato» for this agent. `inherit` (default): the workspace's `voice.agent_register`. `tu`, `usted`, or `mirror` (tú until the customer uses usted); wins over the agent's instructions."
                            }
                          },
                          "required": [
                            "treatment_price",
                            "far_slot_waitlist",
                            "urgency_slots",
                            "specialist_fallback",
                            "silent_handoff"
                          ],
                          "additionalProperties": false
                        },
                        "price_policy_conflicts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "source": {
                                "type": "string",
                                "enum": [
                                  "system_prompt",
                                  "skill"
                                ]
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "excerpt": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "source",
                              "name",
                              "excerpt"
                            ]
                          }
                        }
                      },
                      "required": [
                        "live",
                        "draft",
                        "price_policy_conflicts"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "live": {
                      "treatment_price": "unset",
                      "far_slot_waitlist": false,
                      "urgency_slots": false,
                      "specialist_fallback": false,
                      "silent_handoff": false,
                      "voice_note": {
                        "enabled": false,
                        "voice_id": null,
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.75,
                          "speed": 1,
                          "style": 0,
                          "use_speaker_boost": true
                        },
                        "lexicon": []
                      },
                      "opening_marks": "inherit",
                      "register": "inherit"
                    },
                    "draft": {
                      "treatment_price": "first_ask",
                      "far_slot_waitlist": false,
                      "urgency_slots": true,
                      "specialist_fallback": false,
                      "silent_handoff": true,
                      "opening_marks": "inherit",
                      "register": "inherit"
                    },
                    "price_policy_conflicts": [
                      {
                        "source": "system_prompt",
                        "name": null,
                        "excerpt": "Si pide el precio, respondes con una sola pregunta de calificación y el valor va en tu mensaje siguiente."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/publish": {
      "post": {
        "summary": "Atomically promote the draft onto the live runtime + snapshot a version",
        "description": "Publishing never runs, waits on or is blocked by Agent Evals: evals run only when someone asks for them (`POST /ai-agents/{id}/scenarios/run`). `force` and `skip_eval` are accepted for compatibility and ignored. `GET /ai-agents/{id}/publish-gate` is informational only.\n\n**Optimistic concurrency.** `expected_draft_version` (or `If-Match`) is the `draft_version` you reviewed: if someone staged changes since, the publish is refused with 409 `VERSION_CONFLICT` instead of shipping a draft you have not seen. A draft started on a live config that has since changed (e.g. `PUT /ai-agents/{id}/system-prompt` while it was staged) is refused with 409 `VERSION_CONFLICT` reason `draft_base_stale` — publishing it would silently overwrite that change; send `expected_config_version` equal to the current `config_version` to publish over it deliberately, or discard and restage. The response carries the new live `config_version`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "skip_eval": {
                    "type": "boolean"
                  },
                  "force": {
                    "type": "boolean"
                  },
                  "expected_draft_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The `draft_version` you reviewed (GET /ai-agents/{id}/draft). If someone staged changes after that, publish answers 409 `VERSION_CONFLICT` instead of shipping a draft you have not seen. `If-Match` is accepted as an alternative."
                  },
                  "expected_config_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The live `config_version` you reviewed (GET /ai-agents/{id}). Stale → 409. Also the acknowledgement that lets you publish a draft whose base moved (409 `draft_base_stale`): send the CURRENT config_version to publish over the newer live config deliberately."
                  }
                },
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Live row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas",
                    "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                    "status": "active",
                    "is_default": true,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "knowledge_tags": [
                      "horarios",
                      "garantia"
                    ],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": "a3a3a3a3-1000-4000-8000-000000000008",
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-lines": {
      "get": {
        "summary": "The agent's voice lines (ADR 0053): the PSTN number callers dial and the WhatsApp in-chat call button, each with its own state (none/activating/active/in_call/desync)",
        "description": "Two lines, always both present in the response even when never activated: `pstn` (a dedicated phone number) and `whatsapp` (the in-chat call button on the agent’s WhatsApp number). Each carries `state` (`none | activating | active | in_call | desync`) and, once provisioned, `number`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "lines": [
                      {
                        "kind": "pstn",
                        "state": "active",
                        "e164": "+56229381400",
                        "account_id": "a3a3a3a3-3000-4000-8000-000000000001",
                        "activation": null,
                        "desync": null,
                        "call": null,
                        "handoff_e164": "+56912345678",
                        "extra_line_count": 0,
                        "eligibility": {
                          "can_activate": true,
                          "blocked_reason": null,
                          "remedies": []
                        }
                      },
                      {
                        "kind": "whatsapp",
                        "state": "none",
                        "e164": null,
                        "account_id": null,
                        "activation": null,
                        "desync": null,
                        "call": null,
                        "handoff_e164": null,
                        "extra_line_count": 0,
                        "eligibility": {
                          "can_activate": true,
                          "blocked_reason": null,
                          "remedies": []
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-lines/{kind}/activate": {
      "post": {
        "summary": "Start the activation saga for one line. Async and idempotent; a second call while one is running returns refused_reason=already_running rather than starting a second saga",
        "description": "`kind` is `pstn` or `whatsapp`. Provisioning runs on the speech layer in the background, so this answers **200** with `state: \"activating\"` immediately, not once the line is live — poll `GET /ai-agents/{id}/voice-lines` for the eventual `active`. `refused_reason` is non-null and `state` unchanged when a saga is already running for this line.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pstn",
                "whatsapp"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Line mutation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "kind": "pstn",
                    "state": "activating"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-lines/{kind}/deactivate": {
      "post": {
        "summary": "Take one line off the air. REFUSES with 200 + refused_reason=call_in_progress while a customer is on the line — the call is never dropped",
        "description": "A REFUSAL here is a **200**, not a 4xx — `refused_reason: \"call_in_progress\"` with `state` unchanged means exactly that: a live call is not interrupted by this call. Retry once the call ends.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pstn",
                "whatsapp"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Line mutation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "kind": "pstn",
                    "state": "in_call",
                    "refused_reason": "call_in_progress"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-channel": {
      "get": {
        "summary": "The agent's voice channel config: line status, greeting (locked disclosure + brand), curated voices, handoff, propagation state",
        "description": "One combined read of everything the Voz tab needs: the PSTN line state, the composed greeting (`greeting_brand` is the only editable segment — the AI-disclosure + recording notice is fixed and composed server-side, ADR 0039), the curated voice catalogue `voice_id` picks from, `handoff_destination` (E.164, null when unset), and `propagation` (whether the persisted config has reached the speech layer yet).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice channel state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "account_id": "a3a3a3a3-3000-4000-8000-000000000001",
                    "kind": "elevenlabs_voice",
                    "phone_number": "+56229381400",
                    "enabled": true,
                    "test_calls": {
                      "limit": 5,
                      "used_today": 1,
                      "remaining_today": 4
                    },
                    "greeting": {
                      "disclosure": "Hola, le atiende un asistente virtual. Le comento que esta llamada puede ser grabada.",
                      "brand": "Bienvenido a Mi Empresa",
                      "composed": "Hola, le atiende un asistente virtual de Mi Empresa. Le comento que esta llamada puede ser grabada. ¿En qué le puedo ayudar hoy?"
                    },
                    "voice_id": "EXAVITQu4vr4xnSDxMaL",
                    "voices": [
                      {
                        "voice_id": "EXAVITQu4vr4xnSDxMaL",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Sarah",
                        "region": "US",
                        "language": "en",
                        "description_es": "Suave y profesional. Voz femenina americana."
                      },
                      {
                        "voice_id": "6Gr4AVmTax1pMJO0lHRK",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Catalina",
                        "region": "CL",
                        "language": "es",
                        "description_es": "Chilena, cálida y profesional. Suena como una persona real."
                      }
                    ],
                    "handoff_destination": "+56912345678",
                    "propagation": {
                      "status": "synced",
                      "error": null,
                      "attempted_at": "2026-09-21T10:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update greeting brand segment / curated voice / handoff number (persists Vitrina-side, then best-effort speech-layer propagation)",
        "description": "Every field optional (at least one required). `voice_id` must be one of the ids `GET /ai-agents/{id}/voice-channel`’s `curated_voices` lists — a curated catalogue key, not a per-tenant resource, so it is validated server-side rather than typed as a uuid. Persists Vitrina-side FIRST, then best-effort pushes the change to the speech layer; a push failure leaves `propagation.state` non-`synced` rather than failing the request — retry with `POST /ai-agents/{id}/voice-channel/propagate`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "greeting_brand": {
                    "type": "string",
                    "maxLength": 350
                  },
                  "voice_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "handoff_destination": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  }
                }
              },
              "example": {
                "greeting_brand": "Bienvenido a Mi Empresa",
                "voice_id": "EXAVITQu4vr4xnSDxMaL",
                "handoff_destination": "+56912345678"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Voice channel state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "account_id": "a3a3a3a3-3000-4000-8000-000000000001",
                    "kind": "elevenlabs_voice",
                    "phone_number": "+56229381400",
                    "enabled": true,
                    "test_calls": {
                      "limit": 5,
                      "used_today": 1,
                      "remaining_today": 4
                    },
                    "greeting": {
                      "disclosure": "Hola, le atiende un asistente virtual. Le comento que esta llamada puede ser grabada.",
                      "brand": "Bienvenido a Mi Empresa",
                      "composed": "Hola, le atiende un asistente virtual de Mi Empresa. Le comento que esta llamada puede ser grabada. ¿En qué le puedo ayudar hoy?"
                    },
                    "voice_id": "EXAVITQu4vr4xnSDxMaL",
                    "voices": [
                      {
                        "voice_id": "EXAVITQu4vr4xnSDxMaL",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Sarah",
                        "region": "US",
                        "language": "en",
                        "description_es": "Suave y profesional. Voz femenina americana."
                      },
                      {
                        "voice_id": "6Gr4AVmTax1pMJO0lHRK",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Catalina",
                        "region": "CL",
                        "language": "es",
                        "description_es": "Chilena, cálida y profesional. Suena como una persona real."
                      }
                    ],
                    "handoff_destination": "+56912345678",
                    "propagation": {
                      "status": "synced",
                      "error": null,
                      "attempted_at": "2026-09-21T10:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-channel/propagate": {
      "post": {
        "summary": "Retry pushing the persisted voice config onto the speech layer (when propagation is pending)",
        "description": "Re-attempts the push a `PUT` already tried best-effort. Idempotent — calling it while already `synced` is a no-op that just returns the current state.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Voice channel state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "account_id": "a3a3a3a3-3000-4000-8000-000000000001",
                    "kind": "elevenlabs_voice",
                    "phone_number": "+56229381400",
                    "enabled": true,
                    "test_calls": {
                      "limit": 5,
                      "used_today": 1,
                      "remaining_today": 4
                    },
                    "greeting": {
                      "disclosure": "Hola, le atiende un asistente virtual. Le comento que esta llamada puede ser grabada.",
                      "brand": "Bienvenido a Mi Empresa",
                      "composed": "Hola, le atiende un asistente virtual de Mi Empresa. Le comento que esta llamada puede ser grabada. ¿En qué le puedo ayudar hoy?"
                    },
                    "voice_id": "EXAVITQu4vr4xnSDxMaL",
                    "voices": [
                      {
                        "voice_id": "EXAVITQu4vr4xnSDxMaL",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Sarah",
                        "region": "US",
                        "language": "en",
                        "description_es": "Suave y profesional. Voz femenina americana."
                      },
                      {
                        "voice_id": "6Gr4AVmTax1pMJO0lHRK",
                        "voice_settings": {
                          "stability": 0.5,
                          "similarity_boost": 0.8
                        },
                        "name": "Catalina",
                        "region": "CL",
                        "language": "es",
                        "description_es": "Chilena, cálida y profesional. Suena como una persona real."
                      }
                    ],
                    "handoff_destination": "+56912345678",
                    "propagation": {
                      "status": "synced",
                      "error": null,
                      "attempted_at": "2026-09-22T09:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/skills": {
      "get": {
        "summary": "List the tenant skill library (with per-agent attachment counts)",
        "description": "Every skill in the workspace — the library is shared, not owned by one agent — with `agent_count` (how many agents currently have it attached). `channel_overrides` carries per-channel adjustments to the base `content` (today: `voice`, ADR 0039).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "Skills",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a4a4a4a4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Agendar hora",
                      "description": "Cómo ofrecer y confirmar horarios disponibles.",
                      "content": "Cuando el cliente quiera agendar, ofrece los horarios disponibles y confirma antes de reservar.",
                      "status": "active",
                      "channel_overrides": {},
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-04T19:10:00.000Z",
                      "updated_at": "2026-09-04T19:10:00.000Z",
                      "deleted_at": null,
                      "agent_count": 2
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a skill (optional channel_overrides, v1 key: voice)",
        "description": "Adds a new playbook to the library. It is not attached to any agent yet — attach it with `POST /ai-agents/{id}/skills`, or reach it through `POST /ai-agents/{id}/onboarding/materialise-skills`. Answers 201. Fires `skill.created`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 20000,
                    "default": ""
                  },
                  "channel_overrides": {
                    "type": "object",
                    "properties": {
                      "voice": {
                        "type": "string",
                        "maxLength": 20000
                      },
                      "whatsapp_voice": {
                        "type": "string",
                        "maxLength": 20000
                      }
                    },
                    "additionalProperties": false
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "inactive"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Agendar hora",
                "content": "Cuando el cliente quiera agendar, ofrece los horarios disponibles y confirma antes de reservar."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Skill",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a4a4a4a4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Agendar hora",
                    "description": "Cómo ofrecer y confirmar horarios disponibles.",
                    "content": "Cuando el cliente quiera agendar, ofrece los horarios disponibles y confirma antes de reservar.",
                    "status": "active",
                    "channel_overrides": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-04T19:10:00.000Z",
                    "updated_at": "2026-09-04T19:10:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/skills/{id}": {
      "get": {
        "summary": "Read one skill (base content + per-channel adjustments map)",
        "description": "The full row: base `content` and the `channel_overrides` map. `version` (also `X-Config-Version`) is what `PUT /skills/{id}` takes as `expected_version`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Skill",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a4a4a4a4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Agendar hora",
                    "description": "Cómo ofrecer y confirmar horarios disponibles.",
                    "content": "Cuando el cliente quiera agendar, ofrece los horarios disponibles y confirma antes de reservar.",
                    "status": "active",
                    "channel_overrides": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-04T19:10:00.000Z",
                    "updated_at": "2026-09-04T19:10:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a skill; channel_overrides replaces the whole map when present, omit to leave adjustments untouched",
        "description": "Content-only edit — applies instantly to every agent the skill is attached to; there is no draft/publish step for a skill the way there is for an agent. `channel_overrides`, when present, REPLACES the whole map rather than merging into it. Fires `skill.updated`. Every write appends an immutable row to the skill history (`GET /skills/{id}/versions`) and bumps `version`.\n\n**Optimistic concurrency.** The response carries the resource version in `X-Config-Version` (and `ETag`). Send it back on the next write as `expected_version` (or `If-Match`); a stale value answers **409** `VERSION_CONFLICT` with `error.conflict` = `{resource, reason, expected_version, current_version, changed_fields, current_updated_at, read_path, hint}` and writes nothing. Omitting it still works (logged).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 20000
                  },
                  "channel_overrides": {
                    "type": "object",
                    "properties": {
                      "voice": {
                        "type": "string",
                        "maxLength": 20000
                      },
                      "whatsapp_voice": {
                        "type": "string",
                        "maxLength": 20000
                      }
                    },
                    "additionalProperties": false
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "inactive"
                    ]
                  },
                  "expected_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The skill `version` version you last read (the `X-Config-Version` response header, or the version field in the body). If it is no longer current the write is refused with 409 `VERSION_CONFLICT` and nothing is written; the error carries the current version and a diff hint. Optional for backward compatibility (an omitted value is accepted and logged), but send it: without it two editors silently overwrite each other. The `If-Match` header is accepted as an alternative."
                  }
                }
              },
              "example": {
                "content": "Cuando el cliente quiera agendar, ofrece SOLO horarios de la sucursal más cercana y confirma antes de reservar."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Skill",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a4a4a4a4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Agendar hora",
                    "description": "Cómo ofrecer y confirmar horarios disponibles.",
                    "content": "Cuando el cliente quiera agendar, ofrece SOLO horarios de la sucursal más cercana y confirma antes de reservar.",
                    "status": "active",
                    "channel_overrides": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-04T19:10:00.000Z",
                    "updated_at": "2026-09-04T19:10:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a skill (SOFT; detaches it from every agent)",
        "description": "ADR 0093 — a **soft** delete. The skill leaves the library and every agent it was attached to (its `skill_agent` rows are removed, so the runtime stops injecting it immediately), but the row survives with `status: \"deleted\"` and a `deleted_at` stamp.\n\nIt survives because every `ai_agent_version` references its skills by id: hard-deleting used to leave the version viewer unable to name what the agent knew, and made the next publish of a draft restored from that version fail on the foreign key. Restoring such a version now un-deletes the skill instead.\n\n**204** on success, **404** for an id that does not exist in this workspace. Deleting an already-deleted skill is a 204 no-op. Fires `skill.deleted`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/skills/{id}/versions": {
      "get": {
        "summary": "A skill’s version history (immutable, newest first)",
        "description": "One row per write since history began (a `baseline` row holds the content as it stood then): the full `content`, `channel_overrides`, `version`, who wrote it (`actor_kind`, `actor_id`, `api_key_id`), through which surface (`via`: rest, mcp, coach, proposal, onboarding), which fields changed, and when. Rows are never updated or deleted, so an overwritten playbook can always be recovered. An agent run records the `version` of each skill it used (`GET /conversations/{id}/agent-runs`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Skill versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c5c5c5c5-1000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-1000-4000-8000-000000000001",
                      "skill_id": "c5c5c5c5-1000-4000-8000-000000000001",
                      "version": 2,
                      "name": "Calificar lead y cotizar",
                      "description": "Úsala cuando el cliente pregunta por precios.",
                      "content": "## Precio\nDa el precio a la segunda pregunta.",
                      "channel_overrides": {},
                      "status": "active",
                      "change": "update",
                      "changed_fields": [
                        "content"
                      ],
                      "actor_kind": "api_key",
                      "actor_id": "b2b2b2b2-1000-4000-8000-000000000003",
                      "api_key_id": "b2b2b2b2-1000-4000-8000-000000000003",
                      "via": "rest",
                      "created_at": "2026-10-06T15:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/skills": {
      "get": {
        "summary": "List the skills attached to one agent",
        "description": "The LIVE attachment set for this agent — a subset of the tenant skill library (`GET /skills`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Skills",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a4a4a4a4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Agendar hora",
                      "description": "Cómo ofrecer y confirmar horarios disponibles.",
                      "content": "Cuando el cliente quiera agendar, ofrece los horarios disponibles y confirma antes de reservar.",
                      "status": "active",
                      "channel_overrides": {},
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-04T19:10:00.000Z",
                      "updated_at": "2026-09-04T19:10:00.000Z",
                      "deleted_at": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Attach an existing skill to an agent",
        "description": "Links a skill already in the library to this agent — it does not create one. The skill content itself is shared: editing it (`PUT /skills/{id}`) changes what every attached agent sees. Answers 201; attaching an already-attached skill is idempotent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "skill_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "skill_id"
                ]
              },
              "example": {
                "skill_id": "a4a4a4a4-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Attached",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "attached": true,
                    "skill_id": "a4a4a4a4-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/skills/{skillId}": {
      "delete": {
        "summary": "Detach a skill from an agent (does not delete the skill)",
        "description": "Removes the attachment only — the skill row survives in the library and stays attached to any other agent. To remove the skill entirely, use `DELETE /skills/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "skillId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/status-counts": {
      "get": {
        "summary": "Ticket counts by lifecycle state",
        "description": "One number per state for the whole workspace, plus two cross-cuts: `human` (tickets a person is handling) and `marketing`. `closing` is the grace window between a resolve and the auto-close, so it is not one of the `status` values a ticket ever holds — it is a count of resolved tickets whose close timer is running.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 5,
                    "open": 1,
                    "pending": 0,
                    "snoozed": 0,
                    "resolved": 0,
                    "closed": 4,
                    "closing": 0,
                    "human": 1,
                    "marketing": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/today-counts": {
      "get": {
        "summary": "Ticket counts for tickets opened today",
        "description": "The same buckets as `/tickets/status-counts`, narrowed to tickets OPENED today in the workspace's timezone — the day's workload, not the backlog.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "responses": {
          "200": {
            "description": "Counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 1,
                    "open": 1,
                    "pending": 0,
                    "snoozed": 0,
                    "resolved": 0,
                    "closed": 0,
                    "closing": 0,
                    "human": 1,
                    "marketing": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/stage/{stageId}": {
      "get": {
        "summary": "Tickets on one column of a board",
        "description": "The cards in a column, paged. This is how a board is read: one call per column, with the columns themselves coming from `GET /pipelines/{id}`. A stage of a non-`ticket` board simply has no tickets on it and answers an empty page.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "stageId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "status"
              ],
              "default": "created_at"
            },
            "required": false,
            "name": "sortBy",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "required": false,
            "name": "sortOrder",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tickets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": null,
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": null,
                      "resolved_at": null,
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "page": 1,
                      "limit": 20
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}": {
      "get": {
        "summary": "Get ticket",
        "description": "The ticket row. `origin_conversation_id` is the thread it was opened from; every thread attached to it is `GET /tickets/{id}/conversations`. `display_id` (`T-1247`) is the label people quote — a path accepts it, nothing stores it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Ticket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 6,
                    "display_id": "T-6",
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "assignee_ai_agent_graph_id": null,
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "current_stage_id": "55555555-0000-4000-8000-000000000011",
                    "snoozed_until": null,
                    "reason": "El cliente pide reagendar su visita",
                    "opened_by": "human",
                    "opened_via": "admin_ui",
                    "resolved_by": null,
                    "resolved_at": null,
                    "brand": null,
                    "created_at": "2026-09-22T11:06:47.738Z",
                    "updated_at": "2026-09-22T11:06:54.183Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/assign": {
      "put": {
        "summary": "Assign a ticket to a teammate or a team",
        "description": "Send at least one of `handler`, `assignee_user_id`, `team_id`; an empty body is a 400. `null` on either id CLEARS it, omitting the key leaves it alone — two different requests, and the server tells them apart. Assigning drops a system line in the origin conversation naming the author («Camila», «Camila vía Claude», «CRM propio (API)»).\n\nThis is the key-friendly counterpart of `POST /tickets/{id}/claim`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "handler": {
                    "type": "string",
                    "enum": [
                      "bot",
                      "human",
                      "external"
                    ]
                  },
                  "assignee_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "handler": "human",
                "assignee_user_id": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assigned ticket, as a one-element array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": null,
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": null,
                      "resolved_at": null,
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/claim": {
      "post": {
        "summary": "Claim a ticket for the caller",
        "description": "Requires a workspace member — a Supabase session or a personal token acting as one — since the caller BECOMES the assignee, and an `sk_*` API key has no member behind it to assign the ticket to; it gets 403. An API key assigns to a SPECIFIC member instead: `PUT /tickets/{id}/assign`.\n\nClaiming a ticket somebody else already holds is not an error: the response carries `alreadyClaimed: true` and the current assignee, so two people pressing at once get the same answer rather than a race. Takes `tickets:claim`, not `tickets:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The claim result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "ticketId": "dddddddd-0000-4000-8000-000000000001",
                    "assigneeUserId": "11111111-0000-4000-8000-000000000001",
                    "alreadyClaimed": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/status": {
      "put": {
        "summary": "Set the ticket status",
        "description": "`open` · `pending` · `snoozed` · `resolved` · `closed`. `snoozed_until` is REQUIRED when `status` is `snoozed` and ignored otherwise — `PUT /tickets/{id}/snooze` is the same move with a body that cannot forget the time.\n\nSetting `resolved` here does NOT fire `ticket.resolved`: that Evento is emitted by `PUT /tickets/{id}/close` and by the conversation cascade, which are the paths that also stamp `resolved_by`/`resolved_at`. Resolve through one of those if a receiver is watching.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "pending",
                      "snoozed",
                      "resolved",
                      "closed"
                    ]
                  },
                  "snoozed_until": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "status"
                ]
              },
              "example": {
                "status": "snoozed",
                "snoozed_until": "2026-09-23T14:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated ticket, as a one-element array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "snoozed",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": "2026-09-23T14:00:00.000Z",
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": null,
                      "resolved_at": null,
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/snooze": {
      "put": {
        "summary": "Snooze a ticket until a wake-up time",
        "description": "`until` is an ISO 8601 timestamp. The ticket leaves the open queue and comes back by itself; a new inbound message on any of its conversations wakes it early.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "until": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "until"
                ]
              },
              "example": {
                "until": "2026-09-23T14:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The snoozed ticket, as a one-element array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "snoozed",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": "2026-09-23T14:00:00.000Z",
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": null,
                      "resolved_at": null,
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/stage": {
      "put": {
        "summary": "Move a ticket to another column",
        "description": "Moves the card. The move is rejected with a 400 when the current column's `allowed_transitions_to` does not name the target, or when the target belongs to another board — read them from `GET /conversations/{id}/linked-records` or `GET /pipelines/{id}`.\n\n`reason` is recorded on the transition log beside the author. `suggestion_id` marks a Copilot stage suggestion as accepted, and is honoured only when that suggestion actually targets THIS ticket — a mismatched id is ignored rather than refused, so the move still lands.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stageId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "suggestion_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "stageId"
                ]
              },
              "example": {
                "stageId": "55555555-0000-4000-8000-000000000012",
                "reason": "El equipo ya tiene la boleta"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The moved ticket, as a one-element array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000012",
                      "snoozed_until": null,
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": null,
                      "resolved_at": null,
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/close": {
      "put": {
        "summary": "Resolve a ticket (the manual \"done\" action)",
        "description": "⚠ Despite the path, this RESOLVES: status becomes `resolved`, the handler flips to `human`, `resolved_by`/`resolved_at` are stamped and any snooze is cleared. It is the manual counterpart of the auto-resolve worker, and the path name predates the resolved/closed split. The terminal `closed` state is reached by the auto-close worker a grace window later, or by `PUT /tickets/{id}/status` with `closed`.\n\nThis is the operation that fires `ticket.resolved`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The resolved ticket, as a one-element array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 6,
                      "display_id": "T-6",
                      "status": "resolved",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_ai_agent_graph_id": null,
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": null,
                      "reason": "El cliente pide reagendar su visita",
                      "opened_by": "human",
                      "opened_via": "admin_ui",
                      "resolved_by": "human",
                      "resolved_at": "2026-09-22T11:15:06.653Z",
                      "brand": null,
                      "created_at": "2026-09-22T11:06:47.738Z",
                      "updated_at": "2026-09-22T11:06:54.183Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/conversations": {
      "get": {
        "summary": "The conversations this ticket owns",
        "description": "Every thread attached to the case, across channels — the WhatsApp conversation it started on and the email one the customer opened later. Each row carries the contact identity the inbox shows beside it, so a client does not need a `contacts:read` call to label the list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Linked conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "external_id": "web:visitor-9f2c1a",
                      "channel": "web",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "contact_name": "Rodrigo Pizarro",
                      "contact_phone": "+56977000021",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "last_message_date": "2026-09-22T11:08:10.882Z",
                      "created_at": "2026-09-22T11:06:33.744Z",
                      "updated_at": "2026-09-22T11:06:33.744Z"
                    },
                    {
                      "id": "bbbbbbbb-0000-4000-8000-000000000002",
                      "external_id": "rodrigo.pizarro@example.cl",
                      "channel": "email",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "contact_name": "Rodrigo Pizarro",
                      "contact_phone": "+56977000021",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "last_message_date": "2026-09-22T12:02:11.000Z",
                      "created_at": "2026-09-22T12:01:44.000Z",
                      "updated_at": "2026-09-22T12:02:11.000Z"
                    }
                  ],
                  "meta": {
                    "total": 2,
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/messages": {
      "get": {
        "summary": "One timeline across every conversation on this ticket",
        "description": "The case's whole history, interleaved in time: messages from every attached conversation, each stamped with the `channel` it arrived on, plus the system lines that record assignments and status changes (`type: \"event\"`). `?before` pages backwards; the default page is 500 messages, the maximum 2 000.\n\nThis is a flattened view for reading. For the per-message delivery fields and the `author` of each one, read a conversation directly: `GET /conversations/{id}/messages`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 2000,
              "default": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "before",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The unified ticket timeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "channel": "web",
                      "role": "user",
                      "content": "¿Me pueden confirmar la hora del jueves?",
                      "type": "text",
                      "tool_calls": null,
                      "tool_call_id": null,
                      "created_at": "2026-09-22T11:07:02.824Z"
                    },
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "channel": "web",
                      "role": "system",
                      "content": "CRM propio (API) reasignó la conversación",
                      "type": "event",
                      "tool_calls": null,
                      "tool_call_id": null,
                      "created_at": "2026-09-22T11:06:54.411Z"
                    }
                  ],
                  "meta": {
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "limit": 500
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/pipelines": {
      "get": {
        "summary": "List pipelines",
        "description": "Every board of the workspace. `?include=counts` adds `card_count` to each row: THE WORK IN FLIGHT on that board — \"how many cards would I see if I opened it\" — per kind in that kind's own vocabulary: OPEN leads on a sales board, tickets that are neither resolved nor closed on a ticket board (pending and snoozed still count — deferred work is work), and currently-PLACED units on a vehicle board. It is NOT a historical total, so a long-finished funnel reads 0. Without the parameter the response is byte-identical to what it has always been; an unrecognised `include` value is a 400 rather than a silently ignored filter.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "counts"
              ]
            },
            "required": false,
            "name": "include",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pipelines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas",
                      "description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
                      "slug": "generic-sales",
                      "kind": "sales",
                      "is_fallback": true,
                      "direction": "sell_to_customer",
                      "template_key": "generic_sales",
                      "template_version": 1,
                      "template_locale": "es",
                      "created_at": "2026-09-04T19:07:05.226Z",
                      "updated_at": "2026-09-04T19:07:05.226Z",
                      "card_count": 13
                    },
                    {
                      "id": "44444444-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Soporte",
                      "description": "Sigue una solicitud resoluble desde la clasificación hasta la resolución, por todas las conversaciones que haga falta.",
                      "slug": "generic-support",
                      "kind": "ticket",
                      "is_fallback": true,
                      "direction": "sell_to_customer",
                      "template_key": "generic_support",
                      "template_version": 1,
                      "template_locale": "es",
                      "created_at": "2026-09-04T19:07:05.226Z",
                      "updated_at": "2026-09-04T19:07:05.226Z",
                      "card_count": 4
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create pipeline",
        "description": "Creates an EMPTY board — a board with no stages accepts no cards, so follow it with `POST /stages/bulk`, or start from a shipped template with `POST /pipeline-templates/{key}/apply` instead. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. `is_fallback: true` promotes this board and demotes the current holder of that kind; `false` is ignored (promote the other board instead, so there is never a moment with no fallback). `direction` says who the board's customers are: `sell_to_customer` (we sell to the contact; the default) or `buy_from_customer` (we buy from, or consign for, the contact: acquisition, consignación, buyback). The conversation-driven stage mover picks its qualification questions from it; the board's name never decides it. Boards applied from a template take the template's direction.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000,
                    "default": ""
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "ticket",
                      "sales",
                      "vehicle"
                    ],
                    "default": "ticket"
                  },
                  "is_fallback": {
                    "type": "boolean",
                    "default": false
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "sell_to_customer",
                      "buy_from_customer"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Ventas mayoristas",
                "description": "Oportunidades con empresas, con aprobación de precio.",
                "kind": "sales",
                "is_fallback": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pipeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "44444444-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas mayoristas",
                    "description": "Oportunidades con empresas, con aprobación de precio.",
                    "slug": "ventas-mayoristas",
                    "kind": "sales",
                    "is_fallback": false,
                    "direction": "sell_to_customer",
                    "template_key": null,
                    "template_version": null,
                    "template_locale": null,
                    "created_at": "2026-09-22T08:41:22.273Z",
                    "updated_at": "2026-09-22T08:41:22.273Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/pipeline-templates": {
      "get": {
        "summary": "List pipeline templates",
        "description": "The shipped, versioned board templates, localized to the resolved locale (`es-CL` → `es`, anything unshipped → `en`). Filter by `kind` and by `vertical` — the platform ships `generic` templates plus ones built for a particular vertical (`automotive`, `healthcare`, `real-estate`, `restaurant`, `services`, `ecommerce`), and a workspace may apply any of them. Pass `for_vertical` (the workspace's own vertical) for the picker's default view: the templates built for it plus the generic ones, its own first; omit it for the full catalog. `locales_available` says which languages a template can be materialised in.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "sales",
                "ticket",
                "vehicle"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "generic",
                "automotive",
                "healthcare",
                "real-estate",
                "restaurant",
                "services",
                "ecommerce"
              ]
            },
            "required": false,
            "name": "vertical",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "required": false,
            "name": "for_vertical",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 35
            },
            "required": false,
            "name": "locale",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pipeline templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "generic_sales",
                      "version": 1,
                      "kind": "sales",
                      "vertical": "generic",
                      "name": "Ventas",
                      "description": "Pipeline de ventas general: sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
                      "stage_count": 9,
                      "locale": "es",
                      "locales_available": [
                        "en",
                        "es"
                      ]
                    },
                    {
                      "key": "clinic_treatments",
                      "version": 1,
                      "kind": "sales",
                      "vertical": "healthcare",
                      "name": "Tratamientos",
                      "description": "Pipeline comercial de la clínica: desde una consulta de precio hasta un tratamiento presupuestado, aceptado e iniciado.",
                      "stage_count": 7,
                      "locale": "es",
                      "locales_available": [
                        "en",
                        "es"
                      ]
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/pipeline-templates/{key}/preview": {
      "get": {
        "summary": "Preview a localized pipeline template",
        "description": "The exact board an apply would create: the pipeline name, description and kind, plus every stage with its slug, position, category, derived `is_terminal`, localized name/description and its allowed transitions (as stage KEYS — the uuids only exist once the board does), and `lost_reasons`: the reason list of the lost stage (each `reason` is a `lost_reason` value of `PUT /leads/{id}/lost`; empty = the default reasons). Read-only: previewing never writes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "key",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 35
            },
            "required": false,
            "name": "locale",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0
            },
            "required": false,
            "name": "version",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pipeline template preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "key": "generic_sales",
                    "version": 1,
                    "kind": "sales",
                    "vertical": "generic",
                    "locale": "es",
                    "locales_available": [
                      "en",
                      "es"
                    ],
                    "pipeline": {
                      "name": "Ventas",
                      "description": "Pipeline de ventas general: sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
                      "kind": "sales"
                    },
                    "funnel_steps": [
                      {
                        "key": "contact",
                        "name": "Contacto",
                        "outcome": "progress",
                        "position": 0
                      },
                      {
                        "key": "qualified",
                        "name": "Calificado",
                        "outcome": "progress",
                        "position": 1
                      },
                      {
                        "key": "proposal",
                        "name": "Propuesta",
                        "outcome": "progress",
                        "position": 2
                      },
                      {
                        "key": "won",
                        "name": "Ganado",
                        "outcome": "progress",
                        "position": 3
                      },
                      {
                        "key": "lost",
                        "name": "Perdido",
                        "outcome": "lost",
                        "position": 4
                      }
                    ],
                    "stages": [
                      {
                        "key": "new",
                        "slug": "new",
                        "position": 0,
                        "category": "open",
                        "is_terminal": false,
                        "name": "Nuevo",
                        "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                        "transitions": [
                          "contacted",
                          "won",
                          "lost",
                          "unqualified"
                        ],
                        "funnel_step": "contact"
                      },
                      {
                        "key": "contacted",
                        "slug": "contacted",
                        "position": 1,
                        "category": "open",
                        "is_terminal": false,
                        "name": "Contactado",
                        "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                        "transitions": [
                          "new",
                          "qualified",
                          "won",
                          "lost",
                          "unqualified"
                        ],
                        "funnel_step": "contact"
                      }
                    ],
                    "lost_reasons": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/pipeline-templates/{key}/apply": {
      "post": {
        "summary": "Apply a pipeline template to this workspace",
        "description": "Materialises the template into tenant-owned, fully editable pipeline + stage rows — after this the board is yours, and editing it never diverges from anything. IDEMPOTENT per (workspace, template key): a second call returns the existing board with `already_applied: true` and changes nothing. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. `is_fallback: true` promotes this board and demotes the current holder of that kind; `false` is ignored (promote the other board instead, so there is never a moment with no fallback). 400 when `is_fallback` is asked for and another board of that kind already holds the flag.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "locale": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35
                  },
                  "version": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "name_override": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "is_fallback": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "locale": "es",
                "name_override": "Ventas usados",
                "is_fallback": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Applied pipeline + stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "already_applied": false,
                    "template": {
                      "key": "generic_sales",
                      "version": 1,
                      "locale": "es"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas usados",
                      "description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
                      "slug": "generic-sales",
                      "kind": "sales",
                      "is_fallback": true,
                      "direction": "sell_to_customer",
                      "template_key": "generic_sales",
                      "template_version": 1,
                      "template_locale": "es",
                      "created_at": "2026-09-04T19:07:05.226Z",
                      "updated_at": "2026-09-04T19:07:05.226Z"
                    },
                    "stages": [
                      {
                        "id": "55555555-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Nuevo",
                        "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                        "slug": "new",
                        "position": 0,
                        "category": "open",
                        "is_terminal": false,
                        "won_state": null,
                        "sla_days": null,
                        "allowed_transitions_to": [
                          "55555555-0000-4000-8000-000000000002",
                          "55555555-0000-4000-8000-000000000006",
                          "55555555-0000-4000-8000-000000000007"
                        ],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "new",
                        "funnel_step_key": "contact",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z"
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Contactado",
                        "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                        "slug": "contacted",
                        "position": 1,
                        "category": "open",
                        "is_terminal": false,
                        "won_state": null,
                        "sla_days": 3,
                        "allowed_transitions_to": [
                          "55555555-0000-4000-8000-000000000003",
                          "55555555-0000-4000-8000-000000000006",
                          "55555555-0000-4000-8000-000000000007"
                        ],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "contacted",
                        "funnel_step_key": "contact",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z"
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000006",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Ganado",
                        "description": "La oportunidad se cerró a favor.",
                        "slug": "won",
                        "position": 6,
                        "category": "won",
                        "is_terminal": true,
                        "won_state": "won",
                        "sla_days": null,
                        "allowed_transitions_to": [],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "won",
                        "funnel_step_key": "won",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/pipelines/{id}/funnel-steps": {
      "get": {
        "summary": "Get pipeline funnel steps",
        "description": "The board's Pasos del embudo (ADR 0132): the few ordered steps its stages roll up into when the business reads its funnel, each with the stages it holds in board order — so a step holding several stages carries its per-stage split. The STEP LIST is declared by the board's template; a board with no template of its own (or one this release no longer ships) reads the generic template of its kind and says so with `inherited: true`. `outcome: lost` marks the step that holds lost and unqualified stages, reported apart from the path. `unplaced_stages` lists every stage with no step (or a step the template does not declare) — it is never dropped. Reassign a stage with `PUT /stages/{id}` `{ \"funnel_step_key\": \"<key>\" }` (`null` un-places it); `default_step_key` is what the template would give it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pipeline funnel steps",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "steps_template_key": "clinic_treatments",
                    "inherited": false,
                    "locale": "es",
                    "steps": [
                      {
                        "key": "no_appointment",
                        "name": "Sin cita",
                        "outcome": "progress",
                        "position": 0,
                        "stages": [
                          {
                            "id": "55555555-0000-4000-8000-000000000101",
                            "name": "Nuevo",
                            "slug": "new",
                            "category": "open",
                            "position": 0,
                            "default_step_key": "no_appointment"
                          }
                        ]
                      },
                      {
                        "key": "scheduled",
                        "name": "Agendado",
                        "outcome": "progress",
                        "position": 1,
                        "stages": [
                          {
                            "id": "55555555-0000-4000-8000-000000000102",
                            "name": "Evaluación agendada",
                            "slug": "evaluation-scheduled",
                            "category": "open",
                            "position": 1,
                            "default_step_key": "scheduled"
                          },
                          {
                            "id": "55555555-0000-4000-8000-000000000103",
                            "name": "No asistió",
                            "slug": "no-show",
                            "category": "open",
                            "position": 2,
                            "default_step_key": "scheduled"
                          }
                        ]
                      },
                      {
                        "key": "evaluated",
                        "name": "Evaluado",
                        "outcome": "progress",
                        "position": 2,
                        "stages": [
                          {
                            "id": "55555555-0000-4000-8000-000000000104",
                            "name": "Evaluado",
                            "slug": "evaluated",
                            "category": "open",
                            "position": 3,
                            "default_step_key": "evaluated"
                          }
                        ]
                      },
                      {
                        "key": "sale",
                        "name": "Venta",
                        "outcome": "progress",
                        "position": 3,
                        "stages": [
                          {
                            "id": "55555555-0000-4000-8000-000000000105",
                            "name": "Presupuesto enviado",
                            "slug": "budget-sent",
                            "category": "open",
                            "position": 4,
                            "default_step_key": "sale"
                          },
                          {
                            "id": "55555555-0000-4000-8000-000000000106",
                            "name": "Aceptado",
                            "slug": "budget-accepted",
                            "category": "open",
                            "position": 5,
                            "default_step_key": "sale"
                          },
                          {
                            "id": "55555555-0000-4000-8000-000000000107",
                            "name": "Tratamiento iniciado",
                            "slug": "won",
                            "category": "won",
                            "position": 6,
                            "default_step_key": "sale"
                          }
                        ]
                      },
                      {
                        "key": "lost",
                        "name": "Perdido",
                        "outcome": "lost",
                        "position": 4,
                        "stages": [
                          {
                            "id": "55555555-0000-4000-8000-000000000108",
                            "name": "Perdido",
                            "slug": "lost",
                            "category": "lost",
                            "position": 7,
                            "default_step_key": "lost"
                          },
                          {
                            "id": "55555555-0000-4000-8000-000000000109",
                            "name": "No calificado",
                            "slug": "unqualified",
                            "category": "unqualified",
                            "position": 8,
                            "default_step_key": "lost"
                          }
                        ]
                      }
                    ],
                    "unplaced_stages": [
                      {
                        "id": "55555555-0000-4000-8000-000000000110",
                        "name": "Control post tratamiento",
                        "slug": "control",
                        "category": "open",
                        "position": 9,
                        "default_step_key": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/pipelines/{id}": {
      "get": {
        "summary": "Get pipeline",
        "description": "One board WITH its columns nested under `stage[]`, in position order — the single call that renders a board. A stage's `ai_agent` embed is omitted for a caller without `ai_agents:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pipeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "44444444-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas",
                    "description": "Sigue una oportunidad comercial desde la primera consulta concreta hasta un cierre ganado, perdido o no calificado.",
                    "slug": "generic-sales",
                    "kind": "sales",
                    "is_fallback": true,
                    "direction": "sell_to_customer",
                    "template_key": "generic_sales",
                    "template_version": 1,
                    "template_locale": "es",
                    "created_at": "2026-09-04T19:07:05.226Z",
                    "updated_at": "2026-09-04T19:07:05.226Z",
                    "stage": [
                      {
                        "id": "55555555-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Nuevo",
                        "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                        "slug": "new",
                        "position": 0,
                        "category": "open",
                        "is_terminal": false,
                        "won_state": null,
                        "sla_days": null,
                        "allowed_transitions_to": [
                          "55555555-0000-4000-8000-000000000002",
                          "55555555-0000-4000-8000-000000000006",
                          "55555555-0000-4000-8000-000000000007"
                        ],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "new",
                        "funnel_step_key": "contact",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z",
                        "ai_agent": null
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Contactado",
                        "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                        "slug": "contacted",
                        "position": 1,
                        "category": "open",
                        "is_terminal": false,
                        "won_state": null,
                        "sla_days": 3,
                        "allowed_transitions_to": [
                          "55555555-0000-4000-8000-000000000003",
                          "55555555-0000-4000-8000-000000000006",
                          "55555555-0000-4000-8000-000000000007"
                        ],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "contacted",
                        "funnel_step_key": "contact",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z",
                        "ai_agent": null
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000006",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "pipeline_id": "44444444-0000-4000-8000-000000000001",
                        "name": "Ganado",
                        "description": "La oportunidad se cerró a favor.",
                        "slug": "won",
                        "position": 6,
                        "category": "won",
                        "is_terminal": true,
                        "won_state": "won",
                        "sla_days": null,
                        "allowed_transitions_to": [],
                        "ai_agent_id": null,
                        "ai_agent_graph_id": null,
                        "is_human": false,
                        "is_marketing": false,
                        "template_stage_key": "won",
                        "funnel_step_key": "won",
                        "qualifies_lead": false,
                        "driver": "conversation",
                        "customer_message": null,
                        "created_at": "2026-09-04T19:07:05.226Z",
                        "ai_agent": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update pipeline",
        "description": "Only the fields present in the body are written. `kind` is editable only for a non-fallback, EMPTY board that is not the last of its kind — otherwise 409. A workspace holds exactly one FALLBACK board per kind — the board a card lands on when nobody named one. `is_fallback: true` promotes this board and demotes the current holder of that kind; `false` is ignored (promote the other board instead, so there is never a moment with no fallback). `direction` says who the board's customers are: `sell_to_customer` (we sell to the contact; the default) or `buy_from_customer` (we buy from, or consign for, the contact: acquisition, consignación, buyback). The conversation-driven stage mover picks its qualification questions from it; the board's name never decides it. Boards applied from a template take the template's direction.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "ticket",
                      "sales",
                      "vehicle"
                    ]
                  },
                  "is_fallback": {
                    "type": "boolean"
                  },
                  "direction": {
                    "type": "string",
                    "enum": [
                      "sell_to_customer",
                      "buy_from_customer"
                    ]
                  },
                  "default_team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "description": "Oportunidades con empresas. Requiere aprobación de precio sobre 20 %."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pipeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "44444444-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas mayoristas",
                    "description": "Oportunidades con empresas. Requiere aprobación de precio sobre 20 %.",
                    "slug": "ventas-mayoristas",
                    "kind": "sales",
                    "is_fallback": false,
                    "direction": "sell_to_customer",
                    "template_key": null,
                    "template_version": null,
                    "template_locale": null,
                    "created_at": "2026-09-22T08:41:22.273Z",
                    "updated_at": "2026-09-22T08:41:22.273Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete pipeline",
        "description": "DELETING A BOARD NEVER DELETES ITS CARDS. Every lead and ticket on it is MOVED first — to `?reassign_to=<pipeline id>` (same kind), or to the kind's fallback board when it is omitted — and the response reports how many of each moved and where to. 409 when the board is the fallback of its kind (promote another one first) or the last board of its kind; 400 when `reassign_to` names a board of a different kind.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "reassign_to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Reassignment report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tickets": 0,
                    "leads": 13,
                    "reassignedTo": "44444444-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/funnel-report": {
      "get": {
        "summary": "Get the funnel report",
        "description": "The board's Pasos del embudo (ADR 0132) with the number of leads that reached each one in the period, the conversion from the step before, the split by stage inside each step, and Perdido apart. Counts only: this is the door the aggregates-only Consultor seat reads, next to `boards` lists the sales boards that can be read (id + name) so a board picker needs no other scope; `pipeline_id` selects one. The last step (Venta) carries `money`: what its people paid since they arrived (`null` = more than 2 000 people, or unknown). `leads:read`. WHAT A COUNT MEANS. The cohort is the leads of the board whose person ARRIVED in the period (`from` to `to`, both days inclusive, in the workspace clock): the start of the conversation the lead came from (within 30 days of the lead), else the lead's creation — a conversation lead is minted when the conversation closes, days after the first message. People rows carry it as `arrived_at`. A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the arrival date but how far it got is read as of now: a lead arrived in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The board to read. Defaults to the workspace's sales board."
            },
            "required": false,
            "description": "The board to read. Defaults to the workspace's sales board.",
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the period, inclusive. Defaults to 29 days before `to`."
            },
            "required": false,
            "description": "First day of the period, inclusive. Defaults to 29 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock."
            },
            "required": false,
            "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ],
              "description": "Narrow to one Línea of the clinic (its id from the Líneas list), or `none` for the leads that have no Línea. The Líneas plus `none` add up to the unfiltered cohort. Counts and people follow it."
            },
            "required": false,
            "description": "Narrow to one Línea of the clinic (its id from the Líneas list), or `none` for the leads that have no Línea. The Líneas plus `none` add up to the unfiltered cohort. Counts and people follow it.",
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Funnel report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pipeline_id": "0b1c2d3e-0000-4000-8000-000000000001",
                    "period": {
                      "from": "2026-09-10",
                      "to": "2026-10-09",
                      "timezone": "America/Santiago"
                    },
                    "cohort": 120,
                    "steps": [
                      {
                        "key": "no_appointment",
                        "name": "Sin cita",
                        "position": 0,
                        "reached": 120,
                        "current": 61,
                        "conversion_from_previous": null,
                        "conversion_from_start": 1,
                        "stages": [
                          {
                            "id": "0b1c2d3e-0000-4000-8000-0000000000a1",
                            "name": "Nuevo",
                            "reached": 120,
                            "current": 61
                          }
                        ]
                      },
                      {
                        "key": "scheduled",
                        "name": "Agendado",
                        "position": 1,
                        "reached": 48,
                        "current": 14,
                        "conversion_from_previous": 0.4,
                        "conversion_from_start": 0.4,
                        "stages": [],
                        "money": 3450000
                      }
                    ],
                    "lost": {
                      "key": "lost",
                      "name": "Perdido",
                      "total": 22,
                      "from_steps": [
                        {
                          "step_key": "no_appointment",
                          "step_name": "Sin cita",
                          "count": 15
                        },
                        {
                          "step_key": "scheduled",
                          "step_name": "Agendado",
                          "count": 5
                        },
                        {
                          "step_key": null,
                          "step_name": null,
                          "count": 2
                        }
                      ],
                      "stages": []
                    },
                    "unplaced": 0,
                    "unplaced_stages": [],
                    "boards": [
                      {
                        "id": "0b1c2d3e-0000-4000-8000-000000000001",
                        "name": "Tratamientos"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/funnel-report/by-linea": {
      "get": {
        "summary": "Get the funnel report split by Línea",
        "description": "One row per Línea of the clinic (and «Sin línea», `linea_id: null`, when leads carry none), each with how many of ITS cohort leads reached every step and stand there today. Same cohort and counting rules as `GET /funnel-report` with one more grouping, so the rows add up to it: for every step, `sum(lineas[].steps[].reached) = ` that step’s `reached` in the unsplit report, and `sum(lineas[].cohort) = cohort`. Open a cell’s people with `GET /funnel-report/people` and the same period plus `linea_id` (`none` for «Sin línea»). Counts only (the Consultor seat reads it). WHAT A COUNT MEANS. The cohort is the leads of the board whose person ARRIVED in the period (`from` to `to`, both days inclusive, in the workspace clock): the start of the conversation the lead came from (within 30 days of the lead), else the lead's creation — a conversation lead is minted when the conversation closes, days after the first message. People rows carry it as `arrived_at`. A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the arrival date but how far it got is read as of now: a lead arrived in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The board to read. Defaults to the workspace's sales board."
            },
            "required": false,
            "description": "The board to read. Defaults to the workspace's sales board.",
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the period, inclusive. Defaults to 29 days before `to`."
            },
            "required": false,
            "description": "First day of the period, inclusive. Defaults to 29 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock."
            },
            "required": false,
            "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Funnel report by Línea",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pipeline_id": "0b1c2d3e-0000-4000-8000-000000000001",
                    "period": {
                      "from": "2026-09-10",
                      "to": "2026-10-09",
                      "timezone": "America/Santiago"
                    },
                    "cohort": 120,
                    "steps": [
                      {
                        "key": "no_appointment",
                        "name": "Sin cita",
                        "position": 0
                      },
                      {
                        "key": "scheduled",
                        "name": "Agendado",
                        "position": 1
                      }
                    ],
                    "lineas": [
                      {
                        "linea_id": "0b1c2d3e-0000-4000-8000-0000000000b1",
                        "nombre": "Implantes",
                        "cohort": 70,
                        "steps": [
                          {
                            "key": "no_appointment",
                            "reached": 70,
                            "current": 30,
                            "conversion_from_previous": null
                          },
                          {
                            "key": "scheduled",
                            "reached": 35,
                            "current": 10,
                            "conversion_from_previous": 0.5
                          }
                        ],
                        "lost": 12,
                        "unplaced": 0
                      },
                      {
                        "linea_id": null,
                        "nombre": null,
                        "cohort": 50,
                        "steps": [
                          {
                            "key": "no_appointment",
                            "reached": 50,
                            "current": 31,
                            "conversion_from_previous": null
                          },
                          {
                            "key": "scheduled",
                            "reached": 13,
                            "current": 4,
                            "conversion_from_previous": 0.26
                          }
                        ],
                        "lost": 10,
                        "unplaced": 0
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/funnel-report/people": {
      "get": {
        "summary": "List the people behind a funnel count",
        "description": "The leads behind one number of `GET /funnel-report`, read through the same cohort and the same rules, so the list always has as many rows as the count says (`total`). `measure=reached&step=` is a step headline; `measure=current` is who stands there today; `measure=lost` is Perdido (narrow with `step`, or `lost_from_unknown=true`); `measure=unplaced` is the leads on a stage that belongs to no step. Add `stage_id` for one stage of a step. Newest activity first. Needs `leads:read`; the contact name and phone ride only with `contacts:read`. D60: each person also carries `score` and `score_reason` («qué pasó»), `lost_reason` («motivo»), the next cita (else the last, `cita_at`/`cita_status`), `paid` (CLP the clinic recorded since the person arrived) and, for a clinic, `origin` (Meta naming the ad, Orgánico, Sin dato). WHAT A COUNT MEANS. The cohort is the leads of the board whose person ARRIVED in the period (`from` to `to`, both days inclusive, in the workspace clock): the start of the conversation the lead came from (within 30 days of the lead), else the lead's creation — a conversation lead is minted when the conversation closes, days after the first message. People rows carry it as `arrived_at`. A lead counts in a step when it has ever stood on a stage of that step OR OF A LATER ONE — from its stage history, or from its current stage when it has none — so a lead that skipped a step still counts for the steps before it, and every cohort lead counts in the first step. The cohort is fixed by the arrival date but how far it got is read as of now: a lead arrived in the period and sold next month counts in the last step the next time you look. `current` is where the cohort stands today. `lost` is the cohort leads standing on a lost stage today, split by the step they were lost from; `step_key: null` is history that cannot say (never guessed into a step). `sum(steps[].current) + lost.total + unplaced = cohort`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Pipelines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The board to read. Defaults to the workspace's sales board."
            },
            "required": false,
            "description": "The board to read. Defaults to the workspace's sales board.",
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the period, inclusive. Defaults to 29 days before `to`."
            },
            "required": false,
            "description": "First day of the period, inclusive. Defaults to 29 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock."
            },
            "required": false,
            "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ],
              "description": "Narrow to one Línea of the clinic (its id from the Líneas list), or `none` for the leads that have no Línea. The Líneas plus `none` add up to the unfiltered cohort. Counts and people follow it."
            },
            "required": false,
            "description": "Narrow to one Línea of the clinic (its id from the Líneas list), or `none` for the leads that have no Línea. The Líneas plus `none` add up to the unfiltered cohort. Counts and people follow it.",
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "reached",
                "current",
                "lost",
                "unplaced"
              ],
              "description": "`reached`: leads that reached `step` or a later one (the headline count). `current`: leads standing in `step` today. `lost`: leads standing on a lost stage today. `unplaced`: leads on a stage that belongs to no step."
            },
            "required": true,
            "description": "`reached`: leads that reached `step` or a later one (the headline count). `current`: leads standing in `step` today. `lost`: leads standing on a lost stage today. `unplaced`: leads on a stage that belongs to no step.",
            "name": "measure",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "A step key from the report. Required for `reached` and `current`; for `lost` it narrows to the step the lead was lost from."
            },
            "required": false,
            "description": "A step key from the report. Required for `reached` and `current`; for `lost` it narrows to the step the lead was lost from.",
            "name": "step",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Narrow to one stage of the step (the per-stage split)."
            },
            "required": false,
            "description": "Narrow to one stage of the step (the per-stage split).",
            "name": "stage_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ],
              "description": "`measure=lost` only: the leads whose history does not say which step they were lost from."
            },
            "required": false,
            "description": "`measure=lost` only: the leads whose history does not say which step they were lost from.",
            "name": "lost_from_unknown",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Funnel people",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pipeline_id": "0b1c2d3e-0000-4000-8000-000000000001",
                    "period": {
                      "from": "2026-09-10",
                      "to": "2026-10-09",
                      "timezone": "America/Santiago"
                    },
                    "total": 1,
                    "limit": 50,
                    "offset": 0,
                    "people": [
                      {
                        "lead_id": "0b1c2d3e-0000-4000-8000-0000000000f1",
                        "title": "Implante molar",
                        "status": "open",
                        "contact_id": "0b1c2d3e-0000-4000-8000-0000000000c1",
                        "contact_name": "Camila Rojas",
                        "stage_id": "0b1c2d3e-0000-4000-8000-0000000000a2",
                        "stage_name": "Evaluación agendada",
                        "created_at": "2026-09-12T14:03:00.000Z",
                        "arrived_at": "2026-09-12T14:01:00.000Z",
                        "last_activity_at": "2026-10-08T09:30:00.000Z",
                        "last_move_at": "2026-09-20T16:00:00.000Z",
                        "last_move_stage_name": "Evaluación agendada",
                        "reached_step_key": "scheduled",
                        "phone": "+56 9 1234 5678",
                        "score": 72,
                        "score_reason": "Pidió hora para evaluación de implante; preguntó por cuotas.",
                        "lost_reason": null,
                        "cita_at": "2026-10-14T13:00:00.000Z",
                        "cita_status": "confirmed",
                        "paid": 45000,
                        "origin": {
                          "kind": "meta",
                          "label": "Meta · Implantes 2",
                          "ad_id": "120210000000000101",
                          "ad_name": "Implantes 2",
                          "ad_set_name": "INTERESES",
                          "campaign_name": "IMPLANTES",
                          "first_touch_at": "2026-09-12T14:01:00.000Z",
                          "via": "first_message"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/stages": {
      "get": {
        "summary": "List stages",
        "description": "Every stage of every board in the workspace, across pipelines and kinds — read `pipeline_id` on each row to group them, or `GET /pipelines/{id}`, which returns one board with its columns already nested. `allowed_transitions_to` is the set of stage ids a card may move to from here; empty or omitted means no restriction. A move the graph forbids is refused when the card is moved, not when the stage is saved.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "responses": {
          "200": {
            "description": "Stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                      "slug": "new",
                      "position": 0,
                      "category": "open",
                      "is_terminal": false,
                      "won_state": null,
                      "sla_days": null,
                      "allowed_transitions_to": [
                        "55555555-0000-4000-8000-000000000002",
                        "55555555-0000-4000-8000-000000000006",
                        "55555555-0000-4000-8000-000000000007"
                      ],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "new",
                      "funnel_step_key": "contact",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    },
                    {
                      "id": "55555555-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Contactado",
                      "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                      "slug": "contacted",
                      "position": 1,
                      "category": "open",
                      "is_terminal": false,
                      "won_state": null,
                      "sla_days": 3,
                      "allowed_transitions_to": [
                        "55555555-0000-4000-8000-000000000003",
                        "55555555-0000-4000-8000-000000000006",
                        "55555555-0000-4000-8000-000000000007"
                      ],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "contacted",
                      "funnel_step_key": "contact",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    },
                    {
                      "id": "55555555-0000-4000-8000-000000000006",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Ganado",
                      "description": "La oportunidad se cerró a favor.",
                      "slug": "won",
                      "position": 6,
                      "category": "won",
                      "is_terminal": true,
                      "won_state": "won",
                      "sla_days": null,
                      "allowed_transitions_to": [],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "won",
                      "funnel_step_key": "won",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    }
                  ],
                  "meta": {
                    "total": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update stages in bulk",
        "description": "Reordering a board: an array of `{ id, …fields }`, up to 100. This is the endpoint a drag-and-drop save uses, because positions have to move together. Only the fields present on an element are written; the body is strict, so an unknown or derived key is a 400. `is_terminal` and `won_state` are DERIVED from `category` and are rejected as input (both bodies are strict, so sending either is a 400 naming the key). `sla_days` is the per-stage ageing threshold in days: an integer of at least 1, or `null` for \"no limit\" — omitting the key leaves the current value alone, sending `null` clears it, and those are different requests. `funnel_step_key` places the stage in one of its board's Pasos del embudo (`GET /pipelines/{id}/funnel-steps` lists them); a lost or unqualified stage goes to a `lost` step, every other stage to a `progress` step, else 400 naming the legal keys. On create, omitting it takes the template's default for the category; on update, omitting it leaves it alone, `null` un-places the stage, and a category change that puts the current step on the wrong side re-defaults it. `qualifies_lead` (default `false`) marks the column whose arrival makes the contact a qualified lead for Meta: a lead moved onto it sends `lead_qualified` (`QualifiedLead`, once per contact, no value); on a board that buys from its contacts (`direction: buy_from_customer`) it sends `seller_qualified` instead, and that board's visit step and won column send `seller_visit_booked` / `seller_deal_closed`, never a buyer event. Only an `open` stage of a `sales` board may carry it, else 422 `STAGE_QUALIFIES_LEAD_NOT_ALLOWED`; a category change to an outcome drops it. `driver` says what may move a lead INTO the stage: `fact` (an operational fact such as a confirmed cita in the agenda, a payment, an order or a sale note, a person, or the API; an AI writer only leaves a suggestion), `conversation` (the conversation may move it) or `manual` (a person or the API only; never offered to the AI). Omitted on create = inferred: on a sales board a won stage, or an open one named like «agendada», «reservado», «vendido», «pagado», «firmado», «escritura», «promesa» or «entregado», is `fact`, anything else `conversation`. `customer_message` (default `null`) is the message the customer of a post-venta caso gets when a ticket moves INTO the stage (variables `{caso}`, `{fecha_visita}`, `{contratista}`; sent through the system-outbound gate); on update, omitting it leaves it alone and `null` clears it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "name": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 4000
                    },
                    "ai_agent_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "slug": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80,
                      "pattern": "^[a-z0-9]+(?:[-_][a-z0-9]+)*$"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "open",
                        "won",
                        "lost",
                        "unqualified",
                        "resolved"
                      ]
                    },
                    "sla_days": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "minimum": 1
                    },
                    "allowed_transitions_to": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "maxItems": 100
                    },
                    "funnel_step_key": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "minLength": 1,
                      "maxLength": 60,
                      "pattern": "^[a-z][a-z0-9_]*$"
                    },
                    "qualifies_lead": {
                      "type": "boolean"
                    },
                    "driver": {
                      "type": "string",
                      "enum": [
                        "fact",
                        "conversation",
                        "manual"
                      ]
                    },
                    "customer_message": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "minLength": 1,
                      "maxLength": 1000,
                      "description": "Message to the customer when a post-venta caso moves into this stage. Variables: {caso}, {fecha_visita}, {contratista}."
                    }
                  },
                  "required": [
                    "id"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1,
                "maxItems": 100
              },
              "example": [
                {
                  "id": "55555555-0000-4000-8000-000000000001",
                  "position": 0
                },
                {
                  "id": "55555555-0000-4000-8000-000000000002",
                  "position": 1,
                  "sla_days": 5
                }
              ]
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                      "slug": "new",
                      "position": 0,
                      "category": "open",
                      "is_terminal": false,
                      "won_state": null,
                      "sla_days": null,
                      "allowed_transitions_to": [
                        "55555555-0000-4000-8000-000000000002",
                        "55555555-0000-4000-8000-000000000006",
                        "55555555-0000-4000-8000-000000000007"
                      ],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "new",
                      "funnel_step_key": "contact",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    },
                    {
                      "id": "55555555-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Contactado",
                      "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                      "slug": "contacted",
                      "position": 1,
                      "category": "open",
                      "is_terminal": false,
                      "won_state": null,
                      "sla_days": 5,
                      "allowed_transitions_to": [
                        "55555555-0000-4000-8000-000000000003",
                        "55555555-0000-4000-8000-000000000006",
                        "55555555-0000-4000-8000-000000000007"
                      ],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "contacted",
                      "funnel_step_key": "contact",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`STAGE_QUALIFIES_LEAD_NOT_ALLOWED`: `qualifies_lead: true` on a stage that is not an open stage of a sales pipeline. Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create stage",
        "description": "Adds one column to a board. `slug` is generated from the name when omitted; `category` defaults to `open`. `is_terminal` and `won_state` are DERIVED from `category` and are rejected as input (both bodies are strict, so sending either is a 400 naming the key). `sla_days` is the per-stage ageing threshold in days: an integer of at least 1, or `null` for \"no limit\" — omitting the key leaves the current value alone, sending `null` clears it, and those are different requests. `funnel_step_key` places the stage in one of its board's Pasos del embudo (`GET /pipelines/{id}/funnel-steps` lists them); a lost or unqualified stage goes to a `lost` step, every other stage to a `progress` step, else 400 naming the legal keys. On create, omitting it takes the template's default for the category; on update, omitting it leaves it alone, `null` un-places the stage, and a category change that puts the current step on the wrong side re-defaults it. `qualifies_lead` (default `false`) marks the column whose arrival makes the contact a qualified lead for Meta: a lead moved onto it sends `lead_qualified` (`QualifiedLead`, once per contact, no value); on a board that buys from its contacts (`direction: buy_from_customer`) it sends `seller_qualified` instead, and that board's visit step and won column send `seller_visit_booked` / `seller_deal_closed`, never a buyer event. Only an `open` stage of a `sales` board may carry it, else 422 `STAGE_QUALIFIES_LEAD_NOT_ALLOWED`; a category change to an outcome drops it. `driver` says what may move a lead INTO the stage: `fact` (an operational fact such as a confirmed cita in the agenda, a payment, an order or a sale note, a person, or the API; an AI writer only leaves a suggestion), `conversation` (the conversation may move it) or `manual` (a person or the API only; never offered to the AI). Omitted on create = inferred: on a sales board a won stage, or an open one named like «agendada», «reservado», «vendido», «pagado», «firmado», «escritura», «promesa» or «entregado», is `fact`, anything else `conversation`. `customer_message` (default `null`) is the message the customer of a post-venta caso gets when a ticket moves INTO the stage (variables `{caso}`, `{fecha_visita}`, `{contratista}`; sent through the system-outbound gate); on update, omitting it leaves it alone and `null` clears it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000,
                    "default": ""
                  },
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "ai_agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "pattern": "^[a-z0-9]+(?:[-_][a-z0-9]+)*$"
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "open",
                      "won",
                      "lost",
                      "unqualified",
                      "resolved"
                    ],
                    "default": "open"
                  },
                  "sla_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1
                  },
                  "allowed_transitions_to": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  },
                  "funnel_step_key": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60,
                    "pattern": "^[a-z][a-z0-9_]*$"
                  },
                  "qualifies_lead": {
                    "type": "boolean"
                  },
                  "driver": {
                    "type": "string",
                    "enum": [
                      "fact",
                      "conversation",
                      "manual"
                    ]
                  },
                  "customer_message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Message to the customer when a post-venta caso moves into this stage. Variables: {caso}, {fecha_visita}, {contratista}."
                  }
                },
                "required": [
                  "name",
                  "pipeline_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Contactado",
                "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                "pipeline_id": "44444444-0000-4000-8000-000000000001",
                "position": 1,
                "slug": "contacted",
                "category": "open",
                "sla_days": 3,
                "allowed_transitions_to": [
                  "55555555-0000-4000-8000-000000000003",
                  "55555555-0000-4000-8000-000000000006",
                  "55555555-0000-4000-8000-000000000007"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "55555555-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "name": "Contactado",
                    "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                    "slug": "contacted",
                    "position": 1,
                    "category": "open",
                    "is_terminal": false,
                    "won_state": null,
                    "sla_days": 3,
                    "allowed_transitions_to": [
                      "55555555-0000-4000-8000-000000000003",
                      "55555555-0000-4000-8000-000000000006",
                      "55555555-0000-4000-8000-000000000007"
                    ],
                    "ai_agent_id": null,
                    "ai_agent_graph_id": null,
                    "is_human": false,
                    "is_marketing": false,
                    "template_stage_key": "contacted",
                    "funnel_step_key": "contact",
                    "qualifies_lead": false,
                    "driver": "conversation",
                    "customer_message": null,
                    "created_at": "2026-09-04T19:07:05.226Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`STAGE_QUALIFIES_LEAD_NOT_ALLOWED`: `qualifies_lead: true` on a stage that is not an open stage of a sales pipeline. Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/stages/bulk": {
      "post": {
        "summary": "Create stages in bulk",
        "description": "Up to 100 stages in one call — how a board is laid out in a single request instead of one round trip per column. Each element is exactly the single-create body, so the same defaults and the same strictness apply. Prefer `POST /pipeline-templates/{key}/apply` when a shipped template already describes the board you want.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "description": {
                      "type": "string",
                      "maxLength": 4000,
                      "default": ""
                    },
                    "pipeline_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "ai_agent_id": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "format": "uuid"
                    },
                    "position": {
                      "type": "integer",
                      "minimum": 0
                    },
                    "slug": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 80,
                      "pattern": "^[a-z0-9]+(?:[-_][a-z0-9]+)*$"
                    },
                    "category": {
                      "type": "string",
                      "enum": [
                        "open",
                        "won",
                        "lost",
                        "unqualified",
                        "resolved"
                      ],
                      "default": "open"
                    },
                    "sla_days": {
                      "type": [
                        "integer",
                        "null"
                      ],
                      "minimum": 1
                    },
                    "allowed_transitions_to": {
                      "type": "array",
                      "items": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "maxItems": 100
                    },
                    "funnel_step_key": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "minLength": 1,
                      "maxLength": 60,
                      "pattern": "^[a-z][a-z0-9_]*$"
                    },
                    "qualifies_lead": {
                      "type": "boolean"
                    },
                    "driver": {
                      "type": "string",
                      "enum": [
                        "fact",
                        "conversation",
                        "manual"
                      ]
                    },
                    "customer_message": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "minLength": 1,
                      "maxLength": 1000,
                      "description": "Message to the customer when a post-venta caso moves into this stage. Variables: {caso}, {fecha_visita}, {contratista}."
                    }
                  },
                  "required": [
                    "name",
                    "pipeline_id"
                  ],
                  "additionalProperties": false
                },
                "minItems": 1,
                "maxItems": 100
              },
              "example": [
                {
                  "name": "Nuevo",
                  "pipeline_id": "44444444-0000-4000-8000-000000000001",
                  "position": 0,
                  "category": "open"
                },
                {
                  "name": "Ganado",
                  "pipeline_id": "44444444-0000-4000-8000-000000000001",
                  "position": 6,
                  "category": "won"
                }
              ]
            }
          }
        },
        "responses": {
          "201": {
            "description": "Stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                      "slug": "new",
                      "position": 0,
                      "category": "open",
                      "is_terminal": false,
                      "won_state": null,
                      "sla_days": null,
                      "allowed_transitions_to": [
                        "55555555-0000-4000-8000-000000000002",
                        "55555555-0000-4000-8000-000000000006",
                        "55555555-0000-4000-8000-000000000007"
                      ],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "new",
                      "funnel_step_key": "contact",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    },
                    {
                      "id": "55555555-0000-4000-8000-000000000006",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "name": "Ganado",
                      "description": "La oportunidad se cerró a favor.",
                      "slug": "won",
                      "position": 6,
                      "category": "won",
                      "is_terminal": true,
                      "won_state": "won",
                      "sla_days": null,
                      "allowed_transitions_to": [],
                      "ai_agent_id": null,
                      "ai_agent_graph_id": null,
                      "is_human": false,
                      "is_marketing": false,
                      "template_stage_key": "won",
                      "funnel_step_key": "won",
                      "qualifies_lead": false,
                      "driver": "conversation",
                      "customer_message": null,
                      "created_at": "2026-09-04T19:07:05.226Z"
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`STAGE_QUALIFIES_LEAD_NOT_ALLOWED`: `qualifies_lead: true` on a stage that is not an open stage of a sales pipeline. Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/stages/{id}": {
      "get": {
        "summary": "Get stage",
        "description": "One column, with its category, its ageing threshold and its transition graph. `allowed_transitions_to` is the set of stage ids a card may move to from here; empty or omitted means no restriction. A move the graph forbids is refused when the card is moved, not when the stage is saved.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Stage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "55555555-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "name": "Nuevo",
                    "description": "Existe una consulta concreta, pero todavía no hubo contacto sustantivo ni calificación.",
                    "slug": "new",
                    "position": 0,
                    "category": "open",
                    "is_terminal": false,
                    "won_state": null,
                    "sla_days": null,
                    "allowed_transitions_to": [
                      "55555555-0000-4000-8000-000000000002",
                      "55555555-0000-4000-8000-000000000006",
                      "55555555-0000-4000-8000-000000000007"
                    ],
                    "ai_agent_id": null,
                    "ai_agent_graph_id": null,
                    "is_human": false,
                    "is_marketing": false,
                    "template_stage_key": "new",
                    "funnel_step_key": "contact",
                    "qualifies_lead": false,
                    "driver": "conversation",
                    "customer_message": null,
                    "created_at": "2026-09-04T19:07:05.226Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update stage",
        "description": "Only the fields present in the body are written. `is_terminal` and `won_state` are DERIVED from `category` and are rejected as input (both bodies are strict, so sending either is a 400 naming the key). `sla_days` is the per-stage ageing threshold in days: an integer of at least 1, or `null` for \"no limit\" — omitting the key leaves the current value alone, sending `null` clears it, and those are different requests. `funnel_step_key` places the stage in one of its board's Pasos del embudo (`GET /pipelines/{id}/funnel-steps` lists them); a lost or unqualified stage goes to a `lost` step, every other stage to a `progress` step, else 400 naming the legal keys. On create, omitting it takes the template's default for the category; on update, omitting it leaves it alone, `null` un-places the stage, and a category change that puts the current step on the wrong side re-defaults it. `qualifies_lead` (default `false`) marks the column whose arrival makes the contact a qualified lead for Meta: a lead moved onto it sends `lead_qualified` (`QualifiedLead`, once per contact, no value); on a board that buys from its contacts (`direction: buy_from_customer`) it sends `seller_qualified` instead, and that board's visit step and won column send `seller_visit_booked` / `seller_deal_closed`, never a buyer event. Only an `open` stage of a `sales` board may carry it, else 422 `STAGE_QUALIFIES_LEAD_NOT_ALLOWED`; a category change to an outcome drops it. `driver` says what may move a lead INTO the stage: `fact` (an operational fact such as a confirmed cita in the agenda, a payment, an order or a sale note, a person, or the API; an AI writer only leaves a suggestion), `conversation` (the conversation may move it) or `manual` (a person or the API only; never offered to the AI). Omitted on create = inferred: on a sales board a won stage, or an open one named like «agendada», «reservado», «vendido», «pagado», «firmado», «escritura», «promesa» or «entregado», is `fact`, anything else `conversation`. `customer_message` (default `null`) is the message the customer of a post-venta caso gets when a ticket moves INTO the stage (variables `{caso}`, `{fecha_visita}`, `{contratista}`; sent through the system-outbound gate); on update, omitting it leaves it alone and `null` clears it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "ai_agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "slug": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "pattern": "^[a-z0-9]+(?:[-_][a-z0-9]+)*$"
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "open",
                      "won",
                      "lost",
                      "unqualified",
                      "resolved"
                    ]
                  },
                  "sla_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1
                  },
                  "allowed_transitions_to": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 100
                  },
                  "funnel_step_key": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60,
                    "pattern": "^[a-z][a-z0-9_]*$"
                  },
                  "qualifies_lead": {
                    "type": "boolean"
                  },
                  "driver": {
                    "type": "string",
                    "enum": [
                      "fact",
                      "conversation",
                      "manual"
                    ]
                  },
                  "customer_message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Message to the customer when a post-venta caso moves into this stage. Variables: {caso}, {fecha_visita}, {contratista}."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "position": 2,
                "sla_days": 5,
                "funnel_step_key": "qualified"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Stage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "55555555-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "name": "Contactado",
                    "description": "Alguien del equipo hizo contacto real y está entendiendo la necesidad.",
                    "slug": "contacted",
                    "position": 2,
                    "category": "open",
                    "is_terminal": false,
                    "won_state": null,
                    "sla_days": 5,
                    "allowed_transitions_to": [
                      "55555555-0000-4000-8000-000000000003",
                      "55555555-0000-4000-8000-000000000006",
                      "55555555-0000-4000-8000-000000000007"
                    ],
                    "ai_agent_id": null,
                    "ai_agent_graph_id": null,
                    "is_human": false,
                    "is_marketing": false,
                    "template_stage_key": "contacted",
                    "funnel_step_key": "contact",
                    "qualifies_lead": false,
                    "driver": "conversation",
                    "customer_message": null,
                    "created_at": "2026-09-04T19:07:05.226Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`STAGE_QUALIFIES_LEAD_NOT_ALLOWED`: `qualifies_lead: true` on a stage that is not an open stage of a sales pipeline. Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete stage",
        "description": "DELETING A COLUMN NEVER DELETES THE CARDS ON IT. Every lead and ticket sitting there is MOVED first — to `?reassign_to=<stage id>`, or to the first sibling column of the same board when it is omitted — and the response reports how many of each moved and where to. 409 when the stage is the last one on its board, or when there is no sibling to move the cards into; 400 when `reassign_to` names a stage on another board. The column, its transition graph and its ageing threshold are gone for good; the transition history that mentions it is not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Stages"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "reassign_to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Reassignment report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tickets": 0,
                    "leads": 4,
                    "reassignedTo": "55555555-0000-4000-8000-000000000002"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/kb/sources": {
      "get": {
        "summary": "List KB sources",
        "description": "Every manual KB source in the workspace, newest first. `status` filters to `active` (approved + pending — everything not revoked), `revoked`, or `all`; omit it for `active`. `tag` filters to sources carrying that exact tag. Paginated with `limit`/`offset` (default 50, max 200).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "tenant_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "revoked",
                "all"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "tag",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "KB sources",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a5a5a5a5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "status": "approved",
                      "tags": [
                        "horarios",
                        "atencion"
                      ],
                      "summary": {
                        "title": "Horario de atención",
                        "problem": "El cliente pregunta el horario de atención",
                        "root_cause": null,
                        "solution_steps": [
                          "Confirmar el horario: lunes a viernes de 9:00 a 19:00",
                          "Ofrecer agendar una cita si corresponde"
                        ],
                        "product": null,
                        "language": "es",
                        "notes": null
                      },
                      "chunk_ids": [
                        3801
                      ],
                      "verified_until": null,
                      "error": null,
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "ticket_id": null,
                      "help_center_article_id": null,
                      "help_center_locale": null,
                      "help_center_url": null,
                      "created_at": "2026-09-10T12:00:00.000Z",
                      "updated_at": "2026-09-10T12:05:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50
                    },
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a KB source",
        "description": "Hand-author a KB entry: `summary` is the structured shape the agent quotes from (`title`, `problem`, optional `root_cause`, `solution_steps` — 1 to 50 steps), `tags` is 1 to 20 free-form labels, and `verified_until` optionally marks when the answer should be reviewed again.\n\nThe row is born `status: \"pending\"` and with NO chunks — creating it does not make it retrievable. Nothing is searchable until `POST /kb/sources/{id}/embed` runs (typically after review sets `status: \"approved\"` via `PUT`). Answers 201. Fires `kb.source.create`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "summary": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "problem": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 8000
                      },
                      "root_cause": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 8000
                      },
                      "solution_steps": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 2000
                        },
                        "minItems": 1,
                        "maxItems": 50
                      },
                      "product": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "language": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 20
                      },
                      "notes": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 8000
                      }
                    },
                    "required": [
                      "title",
                      "problem",
                      "solution_steps"
                    ]
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "verified_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "summary",
                  "tags"
                ]
              },
              "example": {
                "summary": {
                  "title": "Horario de atención",
                  "problem": "El cliente pregunta el horario de atención",
                  "solution_steps": [
                    "Confirmar el horario: lunes a viernes de 9:00 a 19:00",
                    "Ofrecer agendar una cita si corresponde"
                  ]
                },
                "tags": [
                  "horarios",
                  "atencion"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created KB source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a5a5a5a5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "status": "pending",
                    "tags": [
                      "horarios",
                      "atencion"
                    ],
                    "summary": {
                      "title": "Horario de atención",
                      "problem": "El cliente pregunta el horario de atención",
                      "root_cause": null,
                      "solution_steps": [
                        "Confirmar el horario: lunes a viernes de 9:00 a 19:00",
                        "Ofrecer agendar una cita si corresponde"
                      ],
                      "product": null,
                      "language": "es",
                      "notes": null
                    },
                    "chunk_ids": [],
                    "verified_until": null,
                    "error": null,
                    "conversation_id": null,
                    "ticket_id": null,
                    "help_center_article_id": null,
                    "help_center_locale": null,
                    "help_center_url": null,
                    "created_at": "2026-09-10T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/kb/sources/{id}": {
      "get": {
        "summary": "Get KB source",
        "description": "The full row, including its `summary` and `chunk_ids`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "KB source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a5a5a5a5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "status": "approved",
                    "tags": [
                      "horarios",
                      "atencion"
                    ],
                    "summary": {
                      "title": "Horario de atención",
                      "problem": "El cliente pregunta el horario de atención",
                      "root_cause": null,
                      "solution_steps": [
                        "Confirmar el horario: lunes a viernes de 9:00 a 19:00",
                        "Ofrecer agendar una cita si corresponde"
                      ],
                      "product": null,
                      "language": "es",
                      "notes": null
                    },
                    "chunk_ids": [
                      3801
                    ],
                    "verified_until": null,
                    "error": null,
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "ticket_id": null,
                    "help_center_article_id": null,
                    "help_center_locale": null,
                    "help_center_url": null,
                    "created_at": "2026-09-10T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a KB source",
        "description": "Every field optional; at least one is required (an empty body is a 400). This is the review step: setting `status: \"approved\"` is what the manual-authoring flow uses to mark a draft entry ready — it does NOT re-embed by itself, so a status change alone does not update what the agent retrieves. Call `POST /kb/sources/{id}/embed` after, whether or not `summary` changed, to push the update into `documentation_chunks`. Fires `kb.source.update`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "summary": {
                    "type": "object",
                    "properties": {
                      "title": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "problem": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 8000
                      },
                      "root_cause": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 8000
                      },
                      "solution_steps": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 2000
                        },
                        "minItems": 1,
                        "maxItems": 50
                      },
                      "product": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "language": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 20
                      },
                      "notes": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 8000
                      }
                    },
                    "required": [
                      "title",
                      "problem",
                      "solution_steps"
                    ]
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "approved",
                      "revoked",
                      "ingested",
                      "failed"
                    ]
                  },
                  "verified_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "status": "approved"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated KB source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a5a5a5a5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "status": "approved",
                    "tags": [
                      "horarios",
                      "atencion"
                    ],
                    "summary": {
                      "title": "Horario de atención",
                      "problem": "El cliente pregunta el horario de atención",
                      "root_cause": null,
                      "solution_steps": [
                        "Confirmar el horario: lunes a viernes de 9:00 a 19:00",
                        "Ofrecer agendar una cita si corresponde"
                      ],
                      "product": null,
                      "language": "es",
                      "notes": null
                    },
                    "chunk_ids": [
                      3801
                    ],
                    "verified_until": null,
                    "error": null,
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "ticket_id": null,
                    "help_center_article_id": null,
                    "help_center_locale": null,
                    "help_center_url": null,
                    "created_at": "2026-09-10T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a KB source",
        "description": "A HARD delete — unlike `DELETE /kb-files/{id}` (soft), the row and its embedded chunks are gone; retrieval can no longer return it and there is no un-delete. To stop the source being retrieved WITHOUT losing it, prefer `POST /kb/sources/{id}/revoke` (`status: \"revoked\"`, row kept). Fires `kb.source.delete`. 204.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/kb/sources/{id}/revoke": {
      "post": {
        "summary": "Revoke KB source",
        "description": "The reversible alternative to delete: sets `status: \"revoked\"`. The row and its chunks survive, but a revoked source is excluded from the default `GET /kb/sources` list (pass `status=all` to see it) — this endpoint does not itself purge `documentation_chunks`, so re-approve and re-embed to bring it back rather than re-creating it. 204.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb/sources/{id}/reingest": {
      "post": {
        "summary": "Re-ingest KB source",
        "description": "Re-queues the source’s LINKED CONVERSATION through the automatic kb-ingestion worker (the same path a resolved ticket triggers) — for a source with no `conversation_id` (a purely manual entry) this is a **400**; use `POST /kb/sources/{id}/embed` instead, which re-embeds the stored `summary` directly. Answers **202** — the work happens on the worker, not synchronously.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enqueued": {
                          "type": "boolean"
                        },
                        "kb_source_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "enqueued",
                        "kb_source_id"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enqueued": true,
                    "kb_source_id": "a5a5a5a5-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb/sources/{id}/embed": {
      "post": {
        "summary": "Embed manual KB entry into documentation_chunks",
        "description": "Renders the source’s `summary` to markdown and (re-)embeds it, synchronously. Deletes any chunks this source previously produced before inserting the new ones, so it is safe to call repeatedly — a source never doubles up in retrieval. This is the ONLY thing that makes a manual entry (or an edit to one) actually retrievable; a source with no `summary` is a 400. Fires `kb.source.embed`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "EmbedResult",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "kb_source_id": "a5a5a5a5-0000-4000-8000-000000000001",
                    "chunks": 2,
                    "chunk_ids": [
                      3801,
                      3802
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb/rollup/refresh": {
      "post": {
        "summary": "Refresh KB chunk hits rollup",
        "description": "Refreshes the `kb_chunk_hits` materialised view — how often each chunk was actually retrieved and cited, behind the \"most-used knowledge\" reporting. Synchronous; safe to call repeatedly.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Base"
        ],
        "responses": {
          "200": {
            "description": "Refresh result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "refreshed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/api-keys": {
      "get": {
        "summary": "List API keys",
        "description": "Every workspace credential minted through this family — an API key (workspace-wide) and a personal token (acts as one member) alike. The plaintext `secret` is never returned again after creation; every other field, including `prefix` (the first characters, for telling keys apart in a list), is safe to display. Also lists the test-mode (`sk_test_`) keys of this workspace's sandbox, marked `livemode: false` and carrying the sandbox's `tenant_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "ApiKeys"
        ],
        "responses": {
          "200": {
            "description": "API keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Website integration",
                      "prefix": "sk_dCG1N",
                      "scopes": [
                        "contacts:read",
                        "conversations:write"
                      ],
                      "created_at": "2026-09-15T20:11:31.575Z",
                      "last_used_at": "2026-09-20T14:02:47.916Z",
                      "expires_at": null,
                      "revoked_at": null,
                      "user_id": null,
                      "rotated_at": null,
                      "livemode": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create API key (returns plaintext secret once)",
        "description": "`scopes` must come from this workspace’s permission catalogue (`GET /me` lists what the caller may grant) — an unknown or misspelled scope is refused with 400, never silently dropped. The response’s `secret` is the ONLY time the plaintext token is shown; store it now, the platform keeps only a hash from then on. `livemode: false` mints an `sk_test_` key bound to this workspace's sandbox: it authenticates only there, so it can never read or write this workspace's real data (409 `SANDBOX_NOT_PROVISIONED` until a sandbox exists — `POST /sandbox/automotive` or `POST /sandbox/clinic`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "ApiKeys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "tenant_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$",
                      "enum": [
                        "ai_agents:read",
                        "ai_agents:write",
                        "ai_agents:simulate",
                        "conversations:read",
                        "conversations:write",
                        "contacts:read",
                        "contacts:write",
                        "tickets:read",
                        "tickets:write",
                        "tickets:claim",
                        "leads:read",
                        "leads:write",
                        "leads:intake",
                        "pipelines:read",
                        "pipelines:write",
                        "stages:read",
                        "stages:write",
                        "functions:read",
                        "functions:write",
                        "kb:read",
                        "kb:write",
                        "messages:read",
                        "messages:write",
                        "messages:send",
                        "tools:read",
                        "tags:read",
                        "tags:write",
                        "api_keys:read",
                        "api_keys:write",
                        "personal_tokens:read",
                        "personal_tokens:write",
                        "notifications:read",
                        "notifications:write",
                        "audit:read",
                        "analytics:read",
                        "analytics:write",
                        "dealer_reports:read",
                        "tenant:read",
                        "tenant:write",
                        "tenant:delete",
                        "webhooks:read",
                        "webhooks:write",
                        "messaging_accounts:read",
                        "messaging_accounts:write",
                        "provisioning:read",
                        "provisioning:write",
                        "voice_drift:read",
                        "worker_failures:read",
                        "worker_failures:write",
                        "dealer_sites:read",
                        "dealer_sites:write",
                        "mcp_servers:read",
                        "mcp_servers:write",
                        "memberships:read",
                        "memberships:write",
                        "teams:read",
                        "teams:write",
                        "help_centers:read",
                        "help_centers:write",
                        "labels:read",
                        "labels:write",
                        "macros:read",
                        "macros:write",
                        "slas:read",
                        "slas:write",
                        "routing:read",
                        "routing:write",
                        "triggers:read",
                        "triggers:write",
                        "followups:read",
                        "followups:write",
                        "followups:manage",
                        "custom_attributes:read",
                        "custom_attributes:write",
                        "companies:read",
                        "companies:write",
                        "roles:read",
                        "roles:write",
                        "integrations:read",
                        "integrations:write",
                        "clinic:read",
                        "clinic:write",
                        "clinic_patients:read",
                        "clinic_patients:write",
                        "clinic_record:read",
                        "clinic_record:write",
                        "clinic_money:read",
                        "clinic_money:write",
                        "clinic_admin:write",
                        "clinic_insights:read",
                        "finanzas:read",
                        "finanzas:write",
                        "finanzas_reports:read",
                        "finanzas:export",
                        "finanzas_admin:write",
                        "finanzas_patients:read",
                        "finanzas_payroll:read",
                        "sandbox:read",
                        "sandbox:write",
                        "ads:read",
                        "ads:write",
                        "healthatom:read",
                        "healthatom:write",
                        "reservo:read",
                        "reservo:write",
                        "rexplus:read",
                        "rexplus:write",
                        "billing:read",
                        "billing:write",
                        "copilot:read",
                        "copilot:write",
                        "erp_copilot:use",
                        "tool_credentials:read",
                        "tool_credentials:write",
                        "tool_invocations:read",
                        "corrections:read",
                        "corrections:write",
                        "marketplace:read",
                        "marketplace:write",
                        "stock:read",
                        "storefront_events:write",
                        "pricing:read",
                        "pricing:write",
                        "roi:read",
                        "appointments:read",
                        "appointments:write",
                        "appointments:delete",
                        "schedule_config:write",
                        "appointments:intake",
                        "appointment_types:read",
                        "appointment_types:write",
                        "service_lifecycle:read",
                        "service_lifecycle:write",
                        "outbound_approvals:read",
                        "outbound_approvals:write",
                        "outbound_holds:read",
                        "outbound_holds:write",
                        "campaigns:read",
                        "campaigns:write",
                        "consignments:read",
                        "consignments:write",
                        "stock_dedup:read",
                        "stock_dedup:write",
                        "stock_photo_mirror:read",
                        "stock_photo_mirror:write",
                        "stock_retirement:read",
                        "stock_retirement:write",
                        "dealership_economics:read",
                        "dealership_economics:write",
                        "document_templates:read",
                        "document_templates:write",
                        "vehicle_registry:read",
                        "vehicle_registry:write",
                        "vehicle_pipeline:read",
                        "vehicle_pipeline:write",
                        "sale_notes:read",
                        "sale_notes:write",
                        "sale_notes:void",
                        "transfer_cases:read",
                        "transfer_cases:write",
                        "credit_applications:read",
                        "credit_applications:write",
                        "seller_compensation:read",
                        "seller_compensation:write",
                        "sales_leaderboard:read",
                        "quotes:read",
                        "quotes:write",
                        "quotes:void",
                        "reservations:read",
                        "reservations:write",
                        "reservations:void",
                        "reservations:dispose_abono",
                        "organization_legal:read",
                        "organization_legal:write",
                        "purchase_notes:read",
                        "purchase_notes:write",
                        "purchase_notes:void",
                        "document_payments:read",
                        "document_payments:write",
                        "tenant_bank_accounts:read",
                        "tenant_bank_accounts:write",
                        "bank_movements:read",
                        "bank_movements:write",
                        "payments:read",
                        "payments:write",
                        "payments:reverse",
                        "orders:read",
                        "orders:write",
                        "orders:checkout",
                        "orders:refund",
                        "price_approval:read",
                        "price_approval:request",
                        "price_approval:approve",
                        "cotizacion_discount:approve",
                        "legal:read",
                        "legal:write"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 197
                  },
                  "expires_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "livemode": {
                    "type": "boolean",
                    "description": "false mints an `sk_test_` key bound to this workspace's sandbox (409 SANDBOX_NOT_PROVISIONED when it has none yet). Default true."
                  }
                },
                "required": [
                  "name",
                  "scopes"
                ]
              },
              "example": {
                "name": "Website integration",
                "scopes": [
                  "contacts:read",
                  "conversations:write"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "API key with secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Website integration",
                    "prefix": "sk_dCG1N",
                    "scopes": [
                      "contacts:read",
                      "conversations:write"
                    ],
                    "created_at": "2026-09-15T20:11:31.575Z",
                    "last_used_at": null,
                    "expires_at": null,
                    "revoked_at": null,
                    "user_id": null,
                    "rotated_at": null,
                    "livemode": true,
                    "secret": "sk_dCG1N…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/api-keys/{id}": {
      "delete": {
        "summary": "Revoke API key",
        "description": "Immediate and irreversible: every request bearing this key’s secret 401s from this point on. There is no un-revoke — mint a new key instead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "ApiKeys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/api-keys/{id}/rotate": {
      "post": {
        "summary": "Rotate API key secret (returns new plaintext secret once)",
        "description": "A NEW secret for the SAME key — same `id`, `name` and `scopes`, so an integration keyed on this id keeps its identity. The OLD secret keeps authenticating for `grace_period_hours` (0-72, default 24) so a redeploy has time to pick up the new one; after that it 401s like any revoked credential. `prefix` changes — it is derived from the new secret. Refuses (404) a revoked or foreign key: rotating cannot bring one back to life. Audited (`api_keys.rotate`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "ApiKeys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "grace_period_hours": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 72
                  }
                }
              },
              "example": {
                "grace_period_hours": 24
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key with the new secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Website integration",
                    "prefix": "sk_9pQ2R",
                    "scopes": [
                      "contacts:read",
                      "conversations:write"
                    ],
                    "created_at": "2026-09-15T20:11:31.575Z",
                    "last_used_at": "2026-09-20T14:02:47.916Z",
                    "expires_at": null,
                    "revoked_at": null,
                    "user_id": null,
                    "rotated_at": "2026-09-22T20:11:31.575Z",
                    "livemode": true,
                    "secret": "sk_9pQ2R…",
                    "grace_period_ends_at": "2026-09-23T20:11:31.575Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/analytics/overview": {
      "get": {
        "summary": "Aggregate counts for a date range",
        "description": "Headline counts for `from`–`to`: conversations, messages, tickets resolved (split `resolved_by_bot`/`_human`/`_auto`), tool calls, distinct contacts, plus two derived rates — `bot_resolution_rate` (bot / every resolution) and `tool_use_per_message`. Optional `ai_agent_id` narrows every figure to one agent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "ai_agent_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Overview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversations_count": 182,
                    "messages_count": 940,
                    "tickets_resolved_count": 140,
                    "resolved_by_bot": 96,
                    "resolved_by_human": 40,
                    "resolved_by_auto": 4,
                    "tool_calls_count": 310,
                    "distinct_contacts_count": 165,
                    "bot_resolution_rate": 0.6857,
                    "tool_use_per_message": 0.3298
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/analytics/tools": {
      "get": {
        "summary": "Top tool calls in a date range",
        "description": "The most-called tools over `from`–`to`, ranked by `call_count` (default top 20, max 200 via `limit`). `function_name` is `null` for a call logged against a tool that has since been deleted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tool leaderboard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "function_id": "b6b6b6b6-1000-4000-8000-000000000001",
                      "function_name": "search_vehicle_stock",
                      "call_count": 214
                    },
                    {
                      "function_id": "b6b6b6b6-1000-4000-8000-000000000002",
                      "function_name": "book_appointment",
                      "call_count": 58
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/analytics/timeseries": {
      "get": {
        "summary": "Day-bucketed series for one metric",
        "description": "`metric` selects one of `conversations` | `messages` | `tickets_resolved` | `tool_calls`; the response is one point per day in `from`–`to` — a day with no activity is a zero-valued point, not an absent one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "conversations",
                "messages",
                "tickets_resolved",
                "tool_calls"
              ]
            },
            "required": true,
            "name": "metric",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Timeseries points",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "bucket": "2026-09-20",
                      "value": 24
                    },
                    {
                      "bucket": "2026-09-21",
                      "value": 31
                    },
                    {
                      "bucket": "2026-09-22",
                      "value": 18
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/analytics/resolution-times": {
      "get": {
        "summary": "Ticket resolution latency stats (avg/p50/p95 + by_resolved_by)",
        "description": "Time-to-resolution over `from`–`to`: `count`/`avg_seconds`/`p50_seconds`/`p95_seconds` overall, then the same four figures again per `resolved_by` value (`bot`/`human`/`auto`) in `by_resolved_by` — so \"how fast is the bot vs. a human\" is one call, not three.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Resolution times",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "count": 140,
                    "avg_seconds": 512.4,
                    "p50_seconds": 260,
                    "p95_seconds": 1840,
                    "by_resolved_by": {
                      "bot": {
                        "count": 96,
                        "avg_seconds": 180.2,
                        "p50_seconds": 120,
                        "p95_seconds": 600
                      },
                      "human": {
                        "count": 40,
                        "avg_seconds": 1120.8,
                        "p50_seconds": 900,
                        "p95_seconds": 3200
                      },
                      "auto": {
                        "count": 4,
                        "avg_seconds": 30.1,
                        "p50_seconds": 28,
                        "p95_seconds": 60
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/analytics/latency": {
      "get": {
        "summary": "p50/p95/p99 latency by kind over the time window",
        "description": "Operational timing, not business metrics: `kind` groups spans by name (`agent_turn`, `tool_call`, …) and each row carries `count` plus its `p50_ms`/`p95_ms`/`p99_ms`. `since` defaults to the last hour; omit `kind` to get every kind.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "kind",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Latency percentiles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "kind": "agent_turn",
                      "p50_ms": 1120,
                      "p95_ms": 3400,
                      "p99_ms": 5600,
                      "count": 940
                    },
                    {
                      "kind": "tool_call",
                      "p50_ms": 340,
                      "p95_ms": 980,
                      "p99_ms": 1800,
                      "count": 310
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts": {
      "get": {
        "summary": "List channels",
        "description": "Every messaging account in the workspace, secrets redacted. Connecting a new one is a browser flow (OAuth, a partner connection or IMAP) — see the `MessagingAccounts` reference — but everything already connected is readable here.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "responses": {
          "200": {
            "description": "Messaging accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e4e4e4e4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "WhatsApp Providencia",
                      "kind": "whatsapp_cloud",
                      "channels": [
                        "whatsapp"
                      ],
                      "config": {
                        "connected_via": "atribu_partner"
                      },
                      "secrets": {
                        "atribu_access_token": "<redacted>"
                      },
                      "enabled": true,
                      "default_team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignment_mode": "auto_round_robin",
                      "assignee_user_ids": [],
                      "assignee_weights": {},
                      "assignee_ai_agent_id": null,
                      "help_center_id": null,
                      "public_number_id": null,
                      "display_phone": null,
                      "voice_attachable": false,
                      "voice_attach_source": null,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}": {
      "get": {
        "summary": "Fetch one channel",
        "description": "The account row, secrets redacted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Messaging account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "WhatsApp Providencia",
                    "kind": "whatsapp_cloud",
                    "channels": [
                      "whatsapp"
                    ],
                    "config": {
                      "connected_via": "atribu_partner"
                    },
                    "secrets": {
                      "atribu_access_token": "<redacted>"
                    },
                    "enabled": true,
                    "default_team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignment_mode": "auto_round_robin",
                    "assignee_user_ids": [],
                    "assignee_weights": {},
                    "assignee_ai_agent_id": null,
                    "help_center_id": null,
                    "public_number_id": null,
                    "display_phone": null,
                    "voice_attachable": false,
                    "voice_attach_source": null,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Disconnect a channel",
        "description": "Tears down the provider-side connection first (best-effort, a no-op for a non-partner kind — so a fan-out subscription never keeps delivering to a connection the workspace already deleted locally), then removes the account row. Every conversation, message and lead the channel already produced is untouched: this ends the CONNECTION, not the history.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/messaging-accounts/email-sender/mine": {
      "get": {
        "summary": "Mailboxes you can send a new email from",
        "description": "Lists the mailboxes the signed-in teammate may send a new email from: their own connected mailbox (the one whose address is their login email) and the workspace principal mailbox. `default_id` is the one used when a send or a draft does not set `from_account_id` — their own mailbox if they have one, otherwise the principal. `can_connect_own` is true when they have no mailbox of their own yet. Pass one of these ids as `from_account_id` to `POST /conversations/email` or to an email draft; any other mailbox is refused.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "responses": {
          "200": {
            "description": "Sendable mailboxes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "options": [
                    {
                      "id": "0b5f4c2e-7d1a-4e9b-9c3f-2a6d8e1f4b70",
                      "name": "María",
                      "address": "maria@ejemplo.cl",
                      "role": "own"
                    },
                    {
                      "id": "5e2a9d14-3c8b-4f60-a1e7-9b4c2d7f0a35",
                      "name": "Ventas",
                      "address": "ventas@ejemplo.cl",
                      "role": "principal"
                    }
                  ],
                  "default_id": "0b5f4c2e-7d1a-4e9b-9c3f-2a6d8e1f4b70",
                  "can_connect_own": false
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}/comment-triggers": {
      "get": {
        "summary": "Keyword rules that auto-DM an Instagram commenter, with their fire counters",
        "description": "The deterministic tier of the comment→DM pipeline: which published word sends which DM, on which posts. Evaluated BEFORE any model call, which is what makes a post saying «comenta PRECIO» actually answer that word rather than probably answer it. `trigger_count` counts DMs actually SENT, not comments matched — a match dropped by the hourly cap or refused by the big-account gate sent nothing and appears instead in the comment→DM ledger under its own outcome. A null or empty `media_ids` means the rule applies to EVERY post on the account.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Comment trigger rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "8c8c8c8c-0000-4000-8000-000000000001",
                      "tenant_id": "6b6b6b6b-0000-4000-8000-000000000001",
                      "messaging_account_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "keyword": "info",
                      "match_mode": "exact_word",
                      "case_sensitive": false,
                      "media_ids": null,
                      "dm_template": "¡Hola! Gracias por comentar ✨ Te escribo por aquí para contarte más. ¿Qué te gustaría saber?",
                      "public_reply_template": "¡Te escribimos por DM! ✨",
                      "enabled": false,
                      "trigger_count": 0,
                      "last_triggered_at": null,
                      "created_at": "2026-10-06T14:00:00.000Z",
                      "updated_at": "2026-10-06T14:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a keyword rule on an Instagram channel",
        "description": "When a public comment on this account contains (or, with `exact_word`, IS) the keyword, send `dm_template` VERBATIM as a private reply — no model rewrites it. Creating a rule sends nothing by itself: every DM it later produces still passes the self-comment drop, Meta's one-private-reply-per-comment limit, the 7-day comment window, the 20/hour per-account cap, the muted-contact and big-account gate, and the suppression that keeps the bot off a thread a teammate is handling. Prefer `exact_word` for anything short: a `contains` rule on \"hora\" fires on \"ahora\". `media_ids` are META media ids and confine the rule to those posts; omit for every post (comments on ADS included). `public_reply_template` (optional, ≤300 chars) is posted PUBLICLY under the comment, verbatim, only after the DM went out — keep it to \"te escribimos por DM\"; a public-reply failure never undoes the DM. A commenter whose contact is blocked/spam or opted out of Instagram messages is skipped (`skipped_opted_out`); a marketing-only STOP does not count. Instagram accounts only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keyword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "dm_template": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 900
                  },
                  "public_reply_template": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "match_mode": {
                    "type": "string",
                    "enum": [
                      "contains",
                      "exact_word",
                      "post_keyword"
                    ],
                    "description": "`contains` (default) / `exact_word` match the rule's own `keyword`. `post_keyword` fires when the comment carries a word the POST itself declares — its caption's «comenta X» (X quoted or in capitals), a word declared on the post (`PUT /instagram-media/{mediaId}/keywords`), or a short comment naming the post's linked treatment. `keyword` is not used (send none)."
                  },
                  "case_sensitive": {
                    "type": "boolean"
                  },
                  "media_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 50
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "dm_template"
                ],
                "additionalProperties": false
              },
              "example": {
                "keyword": "info",
                "match_mode": "exact_word",
                "dm_template": "¡Hola! Gracias por comentar ✨ Te escribo por aquí para contarte más. ¿Qué te gustaría saber?",
                "public_reply_template": "¡Te escribimos por DM! ✨",
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Comment trigger rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "8c8c8c8c-0000-4000-8000-000000000001",
                    "tenant_id": "6b6b6b6b-0000-4000-8000-000000000001",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "keyword": "info",
                    "match_mode": "exact_word",
                    "case_sensitive": false,
                    "media_ids": null,
                    "dm_template": "¡Hola! Gracias por comentar ✨ Te escribo por aquí para contarte más. ¿Qué te gustaría saber?",
                    "public_reply_template": "¡Te escribimos por DM! ✨",
                    "enabled": false,
                    "trigger_count": 0,
                    "last_triggered_at": null,
                    "created_at": "2026-10-06T14:00:00.000Z",
                    "updated_at": "2026-10-06T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}/comment-triggers/{trigger_id}": {
      "delete": {
        "summary": "Delete a keyword rule",
        "description": "Permanent, counter included. To retire a finished campaign while keeping the record of how it performed, PATCH `enabled: false` instead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "trigger_id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit or disable a keyword rule",
        "description": "Only the fields provided change. `enabled: false` retires a rule without deleting it — a finished campaign is worth keeping for the next one, and its `trigger_count` is the record of how the last one went. `media_ids: []` clears the post allow-list back to \"every post\", which is the only way to undo a targeting mistake. A rule on another tenant's channel answers 404, identically to one that never existed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "trigger_id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keyword": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "dm_template": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 900
                  },
                  "public_reply_template": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "match_mode": {
                    "type": "string",
                    "enum": [
                      "contains",
                      "exact_word",
                      "post_keyword"
                    ],
                    "description": "`contains` (default) / `exact_word` match the rule's own `keyword`. `post_keyword` fires when the comment carries a word the POST itself declares — its caption's «comenta X» (X quoted or in capitals), a word declared on the post (`PUT /instagram-media/{mediaId}/keywords`), or a short comment naming the post's linked treatment. `keyword` is not used (send none)."
                  },
                  "case_sensitive": {
                    "type": "boolean"
                  },
                  "media_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 50
                  },
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Comment trigger rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "8c8c8c8c-0000-4000-8000-000000000001",
                    "tenant_id": "6b6b6b6b-0000-4000-8000-000000000001",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "keyword": "info",
                    "match_mode": "exact_word",
                    "case_sensitive": false,
                    "media_ids": null,
                    "dm_template": "¡Hola! Gracias por comentar ✨ Te escribo por aquí para contarte más. ¿Qué te gustaría saber?",
                    "public_reply_template": "¡Te escribimos por DM! ✨",
                    "enabled": true,
                    "trigger_count": 0,
                    "last_triggered_at": null,
                    "created_at": "2026-10-06T14:00:00.000Z",
                    "updated_at": "2026-10-06T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}/comment-dm-public-reply": {
      "get": {
        "summary": "Read the public comment reply for AI-judged comment→DMs",
        "description": "The line posted PUBLICLY under an Instagram/Facebook comment after a DM that no keyword rule produced (the classifier judged the commenter interested and the DM was sent). `null` = no public reply (default). Keyword rules keep their own `public_reply_template`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ public_reply_template }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "public_reply_template": "¡Te escribimos por DM! 📩"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set or clear the public comment reply for AI-judged comment→DMs",
        "description": "Up to 300 characters, posted verbatim under the comment only AFTER the DM went out, never on a comment judged not interested. Visible to everyone: keep it to «¡Te escribimos por DM!», never prices or health details. `null` or blank clears it. A failed public reply never undoes or retries the DM.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "public_reply_template": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                },
                "required": [
                  "public_reply_template"
                ],
                "additionalProperties": false
              },
              "example": {
                "public_reply_template": "¡Te escribimos por DM! 📩"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ public_reply_template }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "public_reply_template": "¡Te escribimos por DM! 📩"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}/email-signature": {
      "get": {
        "summary": "Read an email mailbox's business signature",
        "description": "The signature this mailbox puts on customer email **nobody on the team wrote** — AI replies, First-touch, Seguimientos, Recontacto, reminders and API/MCP sends. A person replying from the inbox signs with their own signature and never gets this one.\n\n`mode`: `auto` (default — built from the workspace branding: name, hosted logo, brand color, and the published address / phone / site), `custom`, or `none`. `auto_preview` is always the automatic signature (so a settings screen can preview it); `effective` is what customers get with the current mode (null = no signature). Email mailboxes only (422 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The mailbox signature",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "mode": {
                          "type": "string",
                          "enum": [
                            "auto",
                            "custom",
                            "none"
                          ]
                        },
                        "custom": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "text": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        },
                        "auto_preview": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        },
                        "effective": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "mode",
                        "custom",
                        "auto_preview",
                        "effective"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "mode": "auto",
                    "custom": null,
                    "auto_preview": {
                      "html": "<div class=\"vitrina-business-signature\" style=\"margin-top:24px;padding-top:16px;border-top:1px solid #e5e7eb;\"><table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse;\"><tr><td style=\"padding:0 16px 0 0;vertical-align:middle;\"><img src=\"https://media.example.com/logo.png\" alt=\"Automotora Ejemplo\" height=\"40\" style=\"display:block;height:40px;width:auto;max-width:160px;border:0;\"></td><td style=\"vertical-align:middle;font-size:13px;line-height:1.5;color:#4b5563;\"><div style=\"font-size:14px;font-weight:600;color:#1d4ed8;\">Automotora Ejemplo</div><div>Av. Ejemplo 123, Santiago</div><div><a href=\"tel:+56900000000\" style=\"color:#4b5563;text-decoration:none;\">+56 9 0000 0000</a> &middot; <a href=\"https://ejemplo.cl\" style=\"color:#1d4ed8;text-decoration:none;\">ejemplo.cl</a></div></td></tr></table></div>",
                      "text": "--\nAutomotora Ejemplo\nAv. Ejemplo 123, Santiago\n+56 9 0000 0000 · ejemplo.cl"
                    },
                    "effective": {
                      "html": "<div class=\"vitrina-business-signature\" style=\"margin-top:24px;padding-top:16px;border-top:1px solid #e5e7eb;\"><table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse;\"><tr><td style=\"padding:0 16px 0 0;vertical-align:middle;\"><img src=\"https://media.example.com/logo.png\" alt=\"Automotora Ejemplo\" height=\"40\" style=\"display:block;height:40px;width:auto;max-width:160px;border:0;\"></td><td style=\"vertical-align:middle;font-size:13px;line-height:1.5;color:#4b5563;\"><div style=\"font-size:14px;font-weight:600;color:#1d4ed8;\">Automotora Ejemplo</div><div>Av. Ejemplo 123, Santiago</div><div><a href=\"tel:+56900000000\" style=\"color:#4b5563;text-decoration:none;\">+56 9 0000 0000</a> &middot; <a href=\"https://ejemplo.cl\" style=\"color:#1d4ed8;text-decoration:none;\">ejemplo.cl</a></div></td></tr></table></div>",
                      "text": "--\nAutomotora Ejemplo\nAv. Ejemplo 123, Santiago\n+56 9 0000 0000 · ejemplo.cl"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set an email mailbox's business signature (auto | custom | none)",
        "description": "`custom` requires `html` or `text`; the html is sanitized with the same policy as composed email and its text twin is derived when omitted. `auto` and `none` discard a stored custom body. Applies to the next send (other worker processes pick it up within a minute). Returns the same shape as the GET.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "custom",
                      "none"
                    ]
                  },
                  "html": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  }
                },
                "required": [
                  "mode"
                ]
              },
              "example": {
                "mode": "none"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The mailbox signature after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "mode": {
                          "type": "string",
                          "enum": [
                            "auto",
                            "custom",
                            "none"
                          ]
                        },
                        "custom": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "text": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        },
                        "auto_preview": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        },
                        "effective": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "html": {
                              "type": "string"
                            },
                            "text": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "html",
                            "text"
                          ]
                        }
                      },
                      "required": [
                        "mode",
                        "custom",
                        "auto_preview",
                        "effective"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "mode": "none",
                    "custom": null,
                    "auto_preview": {
                      "html": "<div class=\"vitrina-business-signature\" style=\"margin-top:24px;padding-top:16px;border-top:1px solid #e5e7eb;\"><table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" border=\"0\" style=\"border-collapse:collapse;\"><tr><td style=\"padding:0 16px 0 0;vertical-align:middle;\"><img src=\"https://media.example.com/logo.png\" alt=\"Automotora Ejemplo\" height=\"40\" style=\"display:block;height:40px;width:auto;max-width:160px;border:0;\"></td><td style=\"vertical-align:middle;font-size:13px;line-height:1.5;color:#4b5563;\"><div style=\"font-size:14px;font-weight:600;color:#1d4ed8;\">Automotora Ejemplo</div><div>Av. Ejemplo 123, Santiago</div><div><a href=\"tel:+56900000000\" style=\"color:#4b5563;text-decoration:none;\">+56 9 0000 0000</a> &middot; <a href=\"https://ejemplo.cl\" style=\"color:#1d4ed8;text-decoration:none;\">ejemplo.cl</a></div></td></tr></table></div>",
                      "text": "--\nAutomotora Ejemplo\nAv. Ejemplo 123, Santiago\n+56 9 0000 0000 · ejemplo.cl"
                    },
                    "effective": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/webhooks": {
      "get": {
        "summary": "List webhook subscriptions",
        "description": "Every subscription in the workspace, newest first, with its secret redacted to the first nine characters. Health is `last_status` + the two failure streaks; `paused_at` set means the platform AUTO-PAUSED it (20 failed deliveries in a row, or 24 h failing) and it receives nothing until `POST /webhooks/{id}/resume`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Subscriptions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "url": "https://example.com/hooks/vitrina",
                      "secret": "whsec_d1d…",
                      "events": [
                        "lead.created",
                        "sla.breached"
                      ],
                      "enabled": true,
                      "description": "ERP sync",
                      "created_at": "2026-09-20T01:41:04.274Z",
                      "updated_at": "2026-09-20T01:41:04.274Z",
                      "last_delivery_at": "2026-09-20T01:42:12.154Z",
                      "last_status": 200,
                      "consecutive_failures": 0,
                      "owner_kind": "api_key",
                      "owner_id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "include_data": true,
                      "paused_at": null,
                      "paused_reason": null,
                      "failing_since": null,
                      "consecutive_failed_deliveries": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a webhook subscription (returns the plaintext secret once)",
        "description": "Subscribes `url` to `events` (names from `GET /webhooks/events`, up to twenty, or `\"*\"` for all — including events added later). The caller becomes the subscription's OWNER.\n\n`include_data` defaults to `false`: every delivery is the notice (resource, changes, author, time, `url`) with `data_omitted: \"not_requested\"`. With `true`, `data` is added when, at dispatch time, the owner holds the event's `read_scope` and sees every record — otherwise `data_omitted` names the reason — and never for a `sensitive` event.\n\nThe response is the ONLY one carrying the full `whsec_…` secret. The URL must resolve to a public address (private, loopback and metadata hosts are refused with 400).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookBody"
              },
              "example": {
                "url": "https://example.com/hooks/vitrina",
                "events": [
                  "lead.created",
                  "sla.breached"
                ],
                "description": "ERP sync"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created subscription, secret included",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "url": "https://example.com/hooks/vitrina",
                    "secret": "whsec_d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1d1",
                    "events": [
                      "lead.created",
                      "sla.breached"
                    ],
                    "enabled": true,
                    "description": "ERP sync",
                    "created_at": "2026-09-20T01:41:04.274Z",
                    "updated_at": "2026-09-20T01:41:04.274Z",
                    "last_delivery_at": null,
                    "last_status": null,
                    "consecutive_failures": 0,
                    "owner_kind": "api_key",
                    "owner_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "include_data": false,
                    "paused_at": null,
                    "paused_reason": null,
                    "failing_since": null,
                    "consecutive_failed_deliveries": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/webhooks/events": {
      "get": {
        "summary": "The webhook event catalogue and the signing contract",
        "description": "Every event this workspace can dispatch, each with a sentence on what it means, the exact producer condition that fires it, its schema `version`, the `resource_type` it is about, the `read_scope` that unlocks `data`, whether it is `sensitive` (always a notice), an informal `data_schema` and a realistic `sample` — plus the signing block: the envelope, the two data modes and every `data_omitted` reason, the four `X-Webhook-*` headers plus `Vitrina-Livemode`, the HMAC scheme with verify pseudocode, the replay window, the retry schedule and the auto-pause thresholds.\n\n**Read this before hard-coding an event name.** `POST /webhooks` accepts only names that appear here (plus `*` for everything), so a picker built from this response cannot offer something the create call will reject.\n\nThe catalogue is a constant, not tenant state: it is identical for every workspace and changes only when Vitrina ships a new event. Nothing here is secret — it needs `webhooks:read` because the rest of the family does, not because the content is sensitive.\n\n**Idempotency.** Every delivery carries `X-Webhook-Event-Id`, and all retries and redeliveries of one event reuse it — so a bulk first import produces one id per car and a consumer can dedupe on it. Event ids are unique per event, never per attempt.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Event catalogue and signing contract",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookEventCatalog"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "events": [
                      {
                        "name": "sending_domain.verified",
                        "version": 1,
                        "resource_type": "sending_domain",
                        "read_scope": "messaging_accounts:read",
                        "sensitive": false,
                        "description": "A tenant email sending domain finished DKIM verification and can now send email campaigns (ADR 0032).",
                        "fires_when": "A sending-domain refresh observes SES flip VerifiedForSendingStatus to true for the first time.",
                        "data_schema": {
                          "id (string, uuid)": "The verified sending domain",
                          "domain (string)": "The domain name"
                        },
                        "sample": {
                          "id": "99999999-0000-4000-8000-000000000005",
                          "domain": "mail.cliente.cl"
                        }
                      }
                    ],
                    "signing": {
                      "envelope": {
                        "id (string, uuid)": "Unique event id; use for idempotency",
                        "type (string)": "One of the events above",
                        "version (integer)": "The schema version of this event type; bumped only on a breaking change to `data`",
                        "livemode (boolean)": "`false` when the event came from a sandbox (test-mode) workspace, `true` otherwise. Mirrored on the `Vitrina-Livemode` header.",
                        "created_at (string, ISO 8601 timestamp)": "When the event was produced",
                        "tenant_id (string, uuid)": "Your workspace id",
                        "resource (object)": "{ type, id, url } — what the event is about; GET `url` with your credential to read it",
                        "changes (object, optional)": "field → { from, to } for the identifiers and states that changed",
                        "author (object)": "{ kind: 'member' | 'api_key' | 'ai_agent' | 'system' | 'contact', id, name, via? } — who did it",
                        "data (object, optional)": "The resource payload (see catalog); present only when the subscription includes data and its owner may read it",
                        "data_omitted (string, optional)": "Why `data` is not in this delivery; exactly one of `data` / `data_omitted` is present"
                      },
                      "data_modes": {
                        "notice": "The default. Every delivery carries the notice — resource, changes, author, time, url — and `data_omitted: \"not_requested\"`.",
                        "data": "«Incluir datos del recurso» (`include_data: true`). `data` is added when, at dispatch time, the subscription owner holds the resource read scope and sees every record; otherwise the delivery is a notice with the reason in `data_omitted`.",
                        "not_requested": "The subscription did not ask for data.",
                        "sensitive": "The event is about a dato sensible (health data). It is always a notice, whatever the subscription asked for.",
                        "missing_scope:<scope>": "The owner does not hold that read scope (e.g. `missing_scope:contacts:read`).",
                        "restricted_visibility": "The owner sees only some records (assigned-only, or some sucursales).",
                        "owner_unavailable": "The owner is no longer an active member, or its API key was revoked or expired. Recreate the subscription, or edit its URL or events, to make yourself its owner."
                      },
                      "headers": {
                        "X-Webhook-Event": "The event name (e.g. lead.created).",
                        "X-Webhook-Event-Id": "Unique event id. Retries reuse this id so you can dedupe.",
                        "X-Webhook-Timestamp": "Unix seconds when the signature was generated.",
                        "X-Webhook-Signature": "t=<unix>,v1=<hex> — HMAC-SHA256 of `${timestamp}.${rawBody}` with your subscription secret.",
                        "Vitrina-Livemode": "`true` or `false`, mirroring the body's `livemode`. Signed as part of the body — verifying the signature verifies this too."
                      },
                      "signature_scheme": "hmac-sha256",
                      "signature_format": "t=<unix>,v1=<hex>",
                      "verify_pseudocode": [
                        "const [ts, sig] = parseHeader('X-Webhook-Signature');  // 't=…,v1=…'",
                        "const expected = hmac_sha256(secret, ts + '.' + rawBody);  // hex",
                        "if (!timing_safe_equal(sig.v1, expected)) reject(401);",
                        "if (Math.abs(now_unix() - ts) > 300) reject(401);  // 5-min replay window"
                      ],
                      "timing_window_seconds": 300,
                      "retry": {
                        "attempts": 5,
                        "backoff": "exponential",
                        "delays_seconds": [
                          5,
                          10,
                          20,
                          40,
                          80
                        ],
                        "note": "All retries reuse the same X-Webhook-Event-Id so your endpoint can be idempotent."
                      },
                      "auto_pause": {
                        "consecutive_failed_deliveries": 20,
                        "failing_hours": 24,
                        "note": "A subscription is paused when 20 events in a row exhaust their retries, or when it has failed for 24 hours without a single success. The workspace admins are notified once. A paused subscription receives nothing until POST /webhooks/{id}/resume; events in between are not queued — read the resources to catch up, or redeliver from the delivery log."
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/webhooks/{id}": {
      "get": {
        "summary": "Get a webhook subscription",
        "description": "One subscription, secret redacted. 404 for an id in another workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "url": "https://example.com/hooks/vitrina",
                    "secret": "whsec_d1d…",
                    "events": [
                      "lead.created",
                      "sla.breached"
                    ],
                    "enabled": true,
                    "description": "ERP sync",
                    "created_at": "2026-09-20T01:41:04.274Z",
                    "updated_at": "2026-09-20T01:41:04.274Z",
                    "last_delivery_at": null,
                    "last_status": null,
                    "consecutive_failures": 0,
                    "owner_kind": "api_key",
                    "owner_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "include_data": false,
                    "paused_at": null,
                    "paused_reason": null,
                    "failing_since": null,
                    "consecutive_failed_deliveries": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a webhook subscription",
        "description": "Partial: send only what changes (at least one field). `enabled: false` stops deliveries and discards queued ones.\n\n**Changing `url`, `events` or `include_data` makes the caller the OWNER.** Otherwise anyone with `webhooks:write` could point a data-carrying subscription at their own server and read what their role cannot. `enabled` and `description` leave the owner as it is.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "*",
                        "ai_agent.publish",
                        "ai_agent_graph.publish",
                        "agent_scenario_run.completed",
                        "agent_scenario_suite.completed",
                        "conversation.created",
                        "conversation.assigned",
                        "conversation.resolved",
                        "message.received",
                        "message.sent",
                        "ticket.opened",
                        "ticket.reopened",
                        "ticket.resolved",
                        "ticket.closed",
                        "sla.breached",
                        "kb.source.create",
                        "kb.source.embed",
                        "kb.source.update",
                        "kb.source.delete",
                        "kb_file.uploaded",
                        "kb_file.deleted",
                        "skill.created",
                        "skill.updated",
                        "skill.deleted",
                        "custom_tool.created",
                        "custom_tool.updated",
                        "custom_tool.deleted",
                        "macro.applied",
                        "cost.budget_alert",
                        "runtime.budget_exceeded",
                        "lead.created",
                        "lead.stage_changed",
                        "lead.pipeline_changed",
                        "lead.assigned",
                        "lead.won",
                        "lead.lost",
                        "lead.unqualified",
                        "order.created",
                        "order.status_changed",
                        "stock.synced",
                        "stock.source_retired",
                        "vehicle.published",
                        "vehicle.unpublished",
                        "vehicle.publish_failed",
                        "vehicle.created",
                        "vehicle.attachment.created",
                        "mercadolibre.order",
                        "mercadolibre.vis_lead",
                        "chileautos.lead",
                        "yapo.lead",
                        "website.lead",
                        "demo_lead.created",
                        "demo_lead.pending_scheduling",
                        "demo_lead.routed",
                        "demo_booking.booked",
                        "demo_booking.rescheduled",
                        "demo_booking.cancelled",
                        "demo_booking.held",
                        "demo_booking.no_show",
                        "consignment.candidate.created",
                        "quote.issued",
                        "quote.voided",
                        "quote.extended",
                        "quote.archived",
                        "reservation.created",
                        "reservation.voided",
                        "reservation.abono_disposed",
                        "reservation.converted",
                        "sale_note.issued",
                        "sale_note.approved",
                        "sale_note.voided",
                        "purchase_note.issued",
                        "purchase_note.corrected",
                        "purchase_note.voided",
                        "payment.recorded",
                        "consignment.created",
                        "consignment.returned",
                        "consignment.sold",
                        "credit_application.recorded",
                        "credit_application.submitted",
                        "credit_application.decided",
                        "credit_application.withdrawn",
                        "price_approval.requested",
                        "price_approval.decided",
                        "contact.created",
                        "contact.updated",
                        "contact.merged",
                        "appointment.booked",
                        "appointment.rescheduled",
                        "appointment.cancelled",
                        "appointment.completed",
                        "appointment.no_show",
                        "appointment.reminded",
                        "appointment.imported",
                        "campaign.sent",
                        "campaign.paused",
                        "sending_domain.verified",
                        "coach.proposals_ready",
                        "coach.finding_ready",
                        "voice.autonomous_call",
                        "safety_hold.placed",
                        "safety_hold.ack_overdue",
                        "safety_hold.first_response_overdue",
                        "team.created",
                        "team.updated",
                        "team.deleted",
                        "custom_role.created",
                        "custom_role.updated",
                        "custom_role.deleted",
                        "macro.created",
                        "macro.updated",
                        "macro.deleted",
                        "assignment_rule.created",
                        "assignment_rule.updated",
                        "assignment_rule.deleted",
                        "sla_policy.created",
                        "sla_policy.updated",
                        "sla_policy.deleted",
                        "automation_trigger.created",
                        "automation_trigger.updated",
                        "automation_trigger.deleted",
                        "sending_domain.created",
                        "sending_domain.deleted",
                        "whatsapp_template.created",
                        "whatsapp_template.status_changed",
                        "whatsapp_template.deleted",
                        "whatsapp_flow.created",
                        "whatsapp_flow.published",
                        "whatsapp_flow.deprecated",
                        "whatsapp_flow.deleted",
                        "whatsapp_flow.completed",
                        "channel.connected",
                        "channel.updated",
                        "channel.disconnected",
                        "membership.created",
                        "membership.updated",
                        "membership.removed",
                        "clinic_professional.created",
                        "clinic_professional.updated",
                        "clinic_professional.deleted",
                        "clinic_service.created",
                        "clinic_service.updated",
                        "clinic_service.deleted",
                        "clinic_price_list.updated",
                        "clinic_pack_purchase.created",
                        "clinic_pack_purchase.session_consumed",
                        "clinic_pack_purchase.cancelled",
                        "clinic_budget.created",
                        "clinic_budget.sent",
                        "clinic_budget.accepted",
                        "clinic_budget.rejected",
                        "clinic_budget.voided",
                        "clinic_budget.mirror_changed",
                        "clinic_cash_session.opened",
                        "clinic_cash_session.closed",
                        "clinic_patient.created",
                        "clinic_patient.updated",
                        "clinic_patient.deleted",
                        "clinic_patient.merged",
                        "clinic_encounter.opened",
                        "clinic_encounter.closed",
                        "clinic_note.signed",
                        "clinic_note.amended",
                        "clinic_consent.signed",
                        "clinic_consent.revoked",
                        "clinic_document.uploaded",
                        "clinic_document.deleted",
                        "clinic_privacy_request.completed"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "include_data": {
                    "type": "boolean",
                    "description": "«Incluir datos del recurso». `false` (the default) = every delivery is the notice: resource, changes, author, time and `url`, with `data_omitted: \"not_requested\"`. `true` = each delivery also carries `data` when, at dispatch time, this subscription's OWNER — whoever created it or last changed its url, events or this flag — holds the resource read scope (`read_scope` in the catalogue) and sees every record; otherwise `data_omitted` says why. An event about a dato sensible is always the notice."
                  }
                }
              },
              "example": {
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated subscription",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "url": "https://example.com/hooks/vitrina",
                    "secret": "whsec_d1d…",
                    "events": [
                      "lead.created",
                      "sla.breached"
                    ],
                    "enabled": false,
                    "description": "ERP sync",
                    "created_at": "2026-09-20T01:41:04.274Z",
                    "updated_at": "2026-09-20T01:41:04.274Z",
                    "last_delivery_at": null,
                    "last_status": null,
                    "consecutive_failures": 0,
                    "owner_kind": "api_key",
                    "owner_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "include_data": false,
                    "paused_at": null,
                    "paused_reason": null,
                    "failing_since": null,
                    "consecutive_failed_deliveries": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a webhook subscription",
        "description": "Removes the subscription and its delivery log. The secret goes with it; rotating a secret is delete-and-recreate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/webhooks/{id}/resume": {
      "post": {
        "summary": "Resume an auto-paused webhook subscription",
        "description": "Lifts the platform's AUTO-PAUSE (`paused_at`), which it sets after 20 failed deliveries in a row — events whose every retry failed — or 24 hours of failures without a single success, and restarts both failure streaks at zero. Events that happened while it was paused were not queued: read the resources to catch up, or redeliver from the delivery log.\n\nIdempotent: resuming a subscription that is not paused returns it unchanged. Audited as `webhooks.resume` when it actually resumes. Separate from `enabled`, which is your own switch.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The subscription, resumed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookSubscription"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "url": "https://example.com/hooks/vitrina",
                    "secret": "whsec_d1d…",
                    "events": [
                      "lead.created",
                      "sla.breached"
                    ],
                    "enabled": true,
                    "description": "ERP sync",
                    "created_at": "2026-09-20T01:41:04.274Z",
                    "updated_at": "2026-09-20T01:41:04.274Z",
                    "last_delivery_at": null,
                    "last_status": null,
                    "consecutive_failures": 0,
                    "owner_kind": "api_key",
                    "owner_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "include_data": false,
                    "paused_at": null,
                    "paused_reason": null,
                    "failing_since": null,
                    "consecutive_failed_deliveries": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/webhooks/{id}/deliveries": {
      "get": {
        "summary": "List webhook delivery attempts",
        "description": "One row per ATTEMPT, newest first — the first place to look when \"the webhooks don't arrive\". `error` is `http_<code>`, `timeout` or the network error; `response_excerpt` is the first 500 characters of what the endpoint answered; `request_payload` is the exact body sent. A manual redelivery appears as a new row with `redelivery_of` set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery attempts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": 54,
                      "subscription_id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "event": "lead.created",
                      "event_id": "99999999-0000-4000-8000-000000000001",
                      "attempt": 1,
                      "status_code": 200,
                      "error": null,
                      "response_excerpt": "{\"ok\":true}",
                      "request_payload": {
                        "id": "99999999-0000-4000-8000-000000000001",
                        "type": "lead.created",
                        "version": 1,
                        "created_at": "2026-09-20T01:42:11.194Z",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "resource": {
                          "type": "lead",
                          "id": "99999999-0000-4000-8000-000000000002",
                          "url": "https://api.vitrinadev.com/api/v1/leads/99999999-0000-4000-8000-000000000002"
                        },
                        "author": {
                          "kind": "api_key",
                          "id": "c1c1c1c1-0000-4000-8000-000000000001",
                          "name": "Website integration"
                        },
                        "data": {
                          "lead_id": "99999999-0000-4000-8000-000000000002",
                          "contact_id": "22222222-0000-4000-8000-000000000001",
                          "pipeline_id": "44444444-0000-4000-8000-000000000001",
                          "stage_id": "55555555-0000-4000-8000-000000000001",
                          "title": "Lead disparado desde el sitio",
                          "intent": "buy",
                          "source": "website",
                          "value_amount": null,
                          "value_currency": "CLP",
                          "owner_user_id": null,
                          "team_id": null
                        }
                      },
                      "latency_ms": 549,
                      "created_at": "2026-09-20T01:42:12.116Z",
                      "redelivery_of": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 50
                    },
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/webhooks/{id}/deliveries/{deliveryId}/redeliver": {
      "post": {
        "summary": "Redeliver one webhook delivery",
        "description": "Sends the delivery's event again — same `X-Webhook-Event-Id`, a fresh signature, ONE attempt — to the subscription's current `url`. Returns 202 once queued; the outcome appears in `GET /webhooks/{id}/deliveries` as a new row with `redelivery_of` = this delivery.\n\n`data` is decided again, against the owner as it stands NOW: an owner who lost the read scope since gets the notice. Works on an auto-paused subscription (to test a fix before resuming); 409 on a disabled one. Audited as `webhooks.redeliver`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Webhooks"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "description": "`id` of a row from `GET /webhooks/{id}/deliveries`."
            },
            "required": true,
            "description": "`id` of a row from `GET /webhooks/{id}/deliveries`.",
            "name": "deliveryId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Redelivery queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WebhookRedeliverResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "subscription_id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "delivery_id": 54,
                    "event": "lead.created",
                    "event_id": "99999999-0000-4000-8000-000000000001",
                    "status": "queued"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/memberships": {
      "get": {
        "summary": "List user memberships for the active tenant",
        "description": "Every active seat in the workspace — role, custom role, account status and the profile identity (`display_name`/`phone`/`avatar_url`) each member carries, plus their sign-in `user_email` and `last_login_at` — when they last signed in (ISO 8601, from Supabase Auth), `null` for a member who never has.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "responses": {
          "200": {
            "description": "Memberships",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "11111111-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "role": "agent",
                      "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                      "custom_role_name": "Vendedor sucursal",
                      "status": "active",
                      "display_name": "Camila Rojas",
                      "phone": "+56912345001",
                      "avatar_url": null,
                      "location_ids": [
                        "b1b1b1b1-0000-4000-8000-000000000001"
                      ],
                      "instagram_handles": [
                        "camila.rojas.autos"
                      ],
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z",
                      "user_email": "camila.rojas@example.cl",
                      "last_login_at": "2026-09-30T14:12:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a user to the tenant with a role",
        "description": "Grants an EXISTING Supabase user (`user_id`) a seat directly, with no invitation step — the caller already knows who they are onboarding. `role` and `custom_role_id` are independent, same as on the invitation endpoints, and both are priced against the caller’s own escalation ceiling: a credential can never grant more than the member behind it holds.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "supervisor",
                      "agent",
                      "consultant",
                      "marketing",
                      "accountant"
                    ]
                  },
                  "custom_role_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "invited_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "email"
                  }
                },
                "required": [
                  "user_id",
                  "role"
                ]
              },
              "example": {
                "user_id": "e6e6e6e6-0000-4000-8000-000000000002",
                "role": "agent",
                "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                "location_ids": [
                  "b1b1b1b1-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "e6e6e6e6-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "user_id": "e6e6e6e6-0000-4000-8000-000000000002",
                    "role": "agent",
                    "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "custom_role_name": "Vendedor sucursal",
                    "status": "active",
                    "display_name": "Camila Rojas",
                    "phone": "+56912345001",
                    "avatar_url": null,
                    "location_ids": [
                      "b1b1b1b1-0000-4000-8000-000000000001"
                    ],
                    "instagram_handles": [
                      "camila.rojas.autos"
                    ],
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/memberships/{id}": {
      "put": {
        "summary": "Change a user role, account status, and/or profile identity (display_name / phone / avatar_url)",
        "description": "Role/status changes are gated by the escalation guards in `membership-guard.service` (rank + grant-ceiling). `display_name`, `phone` and `avatar_url` write the target’s `user_profile` instead — gated by the same act-on-target rank check, but with no self-action restriction (editing your own identity is always allowed). `avatar_url: null` removes the photo; a string must be an http(s) URL (manual set — for a file upload, use `POST /memberships/{id}/avatar`).\n\n`location_ids` is the SUCURSAL DEL MIEMBRO — which branches this person is posted to. It is independent of the role fields (moving branch is not a role change, so it may be sent on its own) and every id must name a `location` in this workspace, inactive ones included; an unknown or foreign id is a **400**. `null` and `[]` both clear the posting. It only takes effect while the member’s role carries `stock_visibility: own_locations`, and a caller who is themselves branch-scoped may only hand out sucursales they can see (**403**).\n\n`instagram_handles` are the member’s OWN Instagram usernames (`@` and an instagram.com URL are accepted; stored lower-case without `@`; anything else is a **400**). It replaces the list; `null` or `[]` clears it. A contact with one of these usernames is the team, not a customer: it is marked (`team_account_*`, see `POST /contacts/{id}/team-account`) — the existing ones right away, a new one as soon as its username is known — so the AI does not answer it and the Ads reports do not count it. Removing a handle does not unmark anybody.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "supervisor",
                      "agent",
                      "consultant",
                      "marketing",
                      "accountant"
                    ]
                  },
                  "custom_role_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "account_status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "inactive",
                      "suspended"
                    ]
                  },
                  "display_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "avatar_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "location_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "instagram_handles": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "maxLength": 200
                    },
                    "maxItems": 10
                  }
                }
              },
              "example": {
                "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                "location_ids": [
                  "b1b1b1b1-0000-4000-8000-000000000001"
                ],
                "instagram_handles": [
                  "@camila.rojas.autos"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "11111111-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "user_id": "11111111-0000-4000-8000-000000000001",
                    "role": "agent",
                    "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "custom_role_name": "Vendedor sucursal",
                    "status": "active",
                    "display_name": "Camila Rojas",
                    "phone": "+56912345001",
                    "avatar_url": null,
                    "location_ids": [
                      "b1b1b1b1-0000-4000-8000-000000000001"
                    ],
                    "instagram_handles": [
                      "camila.rojas.autos"
                    ],
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a user from the tenant",
        "description": "Also disposes of the member’s unfinished work, because leaving it assigned to a removed user puts it in nobody’s inbox. `handover` is `unassign` (default — threads surface under “Sin asignar”, nobody is notified), `user` (one named inheritor, requires `handover_assignee_id`) or `round_robin` (dealt over the tenant’s sellers). Every move goes through the normal assignment path, so the new owner gets the usual notification. Returns the receipt of what moved; resolved/closed conversations and won/lost leads keep their original owner.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "unassign",
                "user",
                "round_robin"
              ]
            },
            "required": false,
            "name": "handover",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "handover_assignee_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "{ mode, conversationsMoved, leadsMoved, perAssignee }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "mode": "user",
                    "conversationsMoved": 6,
                    "leadsMoved": 3,
                    "perAssignee": {
                      "e6e6e6e6-0000-4000-8000-000000000002": 9
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/memberships/{id}/avatar": {
      "post": {
        "summary": "Upload a teammate's avatar (admin-set photo)",
        "description": "multipart/form-data with a `file` part (PNG/JPEG/WEBP/GIF, max 5MB) — same multer config/validation as `POST /me/avatar`, but for a TARGET member rather than the caller. Gated by `memberships:write` AND the same rank guard (`assertCanActOnTarget`) as `PUT /memberships/{id}`: an admin may set an agent’s photo, but not one belonging to a member who outranks them. Stores the file and upserts the target’s `user_profile.avatar_url`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              },
              "example": {
                "file": "(binary file part)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ avatar_url }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "avatar_url": "https://api.vitrinadev.com/api/v1/public/avatars/11111111-0000-4000-8000-000000000001/f3b2a1c4-6d5e-4a8f-9c0b-2e1d3f4a5b6c.jpg"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/memberships/{id}/handover-preview": {
      "get": {
        "summary": "What unfinished work this member holds, and who could inherit it",
        "description": "Read-only. Returns the member’s open conversations and open leads plus the candidate assignees a `round_robin` handover would deal over (active members already present in some routing roster, with their current load). Drives the confirmation dialog on removal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "{ userId, openConversations, openLeads, candidates[] }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "userId": "e6e6e6e6-0000-4000-8000-000000000001",
                    "openConversations": 6,
                    "openLeads": 3,
                    "candidates": [
                      {
                        "userId": "e6e6e6e6-0000-4000-8000-000000000002",
                        "openConversations": 2
                      },
                      {
                        "userId": "11111111-0000-4000-8000-000000000001",
                        "openConversations": 5
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/memberships/invitations": {
      "get": {
        "summary": "List the pending invitations for this workspace",
        "description": "PENDING ONLY — accepted invites are teammates and belong on `GET /memberships`; revoked/expired rows (including the one a resend retires) are noise that made a single resend look like two invitations. Each row carries `custom_role_name` (resolved from `custom_role_id`, null when the role was deleted since) and `accept_url`, the signup link the admin can copy when email delivery is unconfigured or flaky.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "responses": {
          "200": {
            "description": "Invitations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e5e5e5e5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "email": "nuevo.vendedor@autosdelvalle.cl",
                      "role": "agent",
                      "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                      "custom_role_name": "Vendedor sucursal",
                      "location_ids": [
                        "b1b1b1b1-0000-4000-8000-000000000001"
                      ],
                      "invited_by": "11111111-0000-4000-8000-000000000001",
                      "accept_url": "https://app.vitrinadev.com/signup?invite=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
                      "created_at": "2026-09-10T13:00:00.000Z",
                      "expires_at": "2026-09-17T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Invite somebody to the workspace by email",
        "description": "Writes a `tenant_invitation` and sends the email; the link is also returned as `accept_url`, so a workspace with no mail provider can still onboard. A pending invitation for the same address is revoked first, so this is also \"re-invite\".\n\n`role` and `custom_role_id` are INDEPENDENT, exactly as on `POST /memberships`: `role` is the escalation rank, the custom role decides effective access on accept. Both are priced against the caller’s own ceiling here rather than at accept — the accept is a token-only request and has no admin to charge.\n\n`location_ids` is the SUCURSAL DEL MIEMBRO the invitee lands posted to (ADR 0102), copied onto `workspace_member.location_ids` when they click through, so a «Solo sus sucursales» seat is narrowed from the first login. Same rules as `PUT /memberships/{id}`: every id must name a `location` in this workspace, inactive ones included; an unknown or foreign id is a **400**; `null`/`[]` mean \"posted nowhere\"; and a caller who is themselves branch-scoped may only hand out branches they can see (**403**).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 320,
                    "format": "email"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "owner",
                      "admin",
                      "supervisor",
                      "agent",
                      "consultant",
                      "marketing",
                      "accountant"
                    ]
                  },
                  "custom_role_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "email",
                  "role"
                ]
              },
              "example": {
                "email": "nuevo.vendedor@autosdelvalle.cl",
                "role": "agent",
                "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                "location_ids": [
                  "b1b1b1b1-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Invitation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "e5e5e5e5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "email": "nuevo.vendedor@autosdelvalle.cl",
                    "role": "agent",
                    "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "custom_role_name": "Vendedor sucursal",
                    "location_ids": [
                      "b1b1b1b1-0000-4000-8000-000000000001"
                    ],
                    "invited_by": "11111111-0000-4000-8000-000000000001",
                    "accept_url": "https://app.vitrinadev.com/signup?invite=a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
                    "created_at": "2026-09-10T13:00:00.000Z",
                    "expires_at": "2026-09-17T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/memberships/invitations/{id}/resend": {
      "post": {
        "summary": "Re-issue a pending invitation with a fresh token and email",
        "description": "Revokes the old row and writes a new one carrying the SAME role, custom role, sucursales, name and phone — a resend is the same invitation, not a blank one. Only the fresh row is listed, so the \"Invitados\" tab still shows one line. An already-accepted invitation is a **400**.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Invitation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e5e5e5e5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "email": "nuevo.vendedor@autosdelvalle.cl",
                    "role": "agent",
                    "custom_role_id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "custom_role_name": "Vendedor sucursal",
                    "location_ids": [
                      "b1b1b1b1-0000-4000-8000-000000000001"
                    ],
                    "invited_by": "11111111-0000-4000-8000-000000000001",
                    "accept_url": "https://app.vitrinadev.com/signup?invite=b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5",
                    "created_at": "2026-09-10T13:00:00.000Z",
                    "expires_at": "2026-09-17T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/memberships/invitations/{id}": {
      "delete": {
        "summary": "Revoke a pending invitation",
        "description": "The token stops working immediately. Tenant-scoped: an id from another workspace is a **404**, not a silent no-op.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Memberships"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/messaging-accounts/{id}/health": {
      "get": {
        "summary": "Live channel identity & health",
        "description": "Dials the provider (or the partner connection provider, for a partner-connected account) right now — the connected phone/IG account/mailbox identity, webhook delivery state, and WhatsApp account health (quality rating, messaging limit) where applicable. `stored` rides along: the last health snapshot the periodic checker wrote, for comparing a live read against what was already known. `stored.reasonCode: send_refused` with `stored.aiRepliesPaused: true` means Meta refuses every send from the number (replies included), so its AI is paused until one send gets through or the channel is reconnected. `?refresh=true` forces a fresh probe instead of the provider’s own cache. A live check that itself fails (provider down, expired token) still answers 200, with a fallback `identity`/`webhook` reporting the failure — this call never breaks a health dashboard by 500ing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "refresh",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel identity & health",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "channel": "whatsapp",
                    "kind": "whatsapp",
                    "connected_via": "atribu_partner",
                    "identity": {
                      "phone_number": "+56912345000",
                      "display_name": "Autos del Valle"
                    },
                    "webhook": {
                      "status": "delivering",
                      "last_event_at": "2026-09-15T18:20:00.000Z"
                    },
                    "whatsapp": {
                      "quality_rating": "GREEN",
                      "messaging_limit": "TIER_10K"
                    },
                    "stored": {
                      "status": "healthy",
                      "reason": null,
                      "reasonCode": null,
                      "lastCheckedAt": "2026-09-15T18:00:00.000Z",
                      "lastOkAt": "2026-09-15T18:00:00.000Z",
                      "unhealthySince": null,
                      "consecutiveFailures": 0,
                      "updatedAt": "2026-09-15T18:00:00.000Z",
                      "aiRepliesPaused": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/agent-options": {
      "get": {
        "summary": "List the agents a new channel can be answered by",
        "description": "The workspace's active AI agents, most-used first (agent runs in the last 90 days), with the preselected one. When a channel is connected without choosing an agent, a workspace with exactly one active agent gets it pinned automatically on WhatsApp, Instagram, Messenger, TikTok, web chat and email; with two or more, `choice_required` is true and the agent must be set on the new channel (`assignee_ai_agent_id`). On email, the pinned agent only ever answers mail classified as a customer.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "responses": {
          "200": {
            "description": "Agents available for a channel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "agents": [
                      {
                        "id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "name": "Asistente de ventas",
                        "runs": 412
                      },
                      {
                        "id": "a3a3a3a3-0000-4000-8000-000000000002",
                        "name": "Asistente de postventa",
                        "runs": 37
                      }
                    ],
                    "suggested_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "choice_required": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/stats": {
      "get": {
        "summary": "Per-account traffic stats",
        "description": "One row per account **that has traffic in the last 30 days** — an idle account is absent rather than present with zeros, so join against `GET /messaging-accounts` for a complete list. Registered before `/{id}` on the router so `stats` is never read as an account id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "responses": {
          "200": {
            "description": "Traffic stats per account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                      "conversations_total": 128,
                      "messages_30d": 640,
                      "inbound_30d": 410,
                      "outbound_30d": 230,
                      "last_message_at": "2026-09-15T18:20:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/{id}/ai-availability": {
      "get": {
        "summary": "Read the channel AI availability policy and current status",
        "description": "Returns the per-channel coverage clock and its current evaluation. Schedule availability does not override agent assignment, channel kill switches, or a human takeover. An account without a saved policy reports `configured: false` and preserves the legacy always-eligible schedule. `policy.timezone` and `status.effective_timezone` report the server-resolved clock inherited from General settings or selected business hours. `policy.human_silence_resume` and `policy.handoff_renotify` are always present in the response with defaults applied (resume: enabled, 30 minutes; re-notify: disabled, 30 minutes once enabled). `human_silence_resume` is RETIRED: since 2026-10-25 a conversation a person handles is silent until «Devolver a la IA», so the channel switch no longer makes the AI answer. Whether the AI answers a waiting customer is the workspace setting `after_handoff.ai_answers_waiting` (GET /followups/recontacto), for every tenant.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "AI availability policy and status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "policy": {
                      "mode": "outside_business_hours",
                      "resume_human_conversations": false,
                      "business_hours_source": "workspace",
                      "team_id": null,
                      "weekly_windows": [],
                      "human_silence_resume": {
                        "enabled": true,
                        "minutes": 30
                      },
                      "handoff_renotify": {
                        "enabled": false,
                        "minutes": 30
                      },
                      "timezone": "America/Santiago"
                    },
                    "status": {
                      "configured": true,
                      "available": false,
                      "reason": "inside_business_hours",
                      "window_start": "2026-09-15T12:00:00.000Z",
                      "window_end": "2026-09-15T22:00:00.000Z",
                      "next_transition": "2026-09-15T22:00:00.000Z",
                      "effective_timezone": "America/Santiago",
                      "policy_revision": "sha256:<revision>"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Set the channel AI availability policy",
        "description": "Persists an always, outside-business-hours, or custom weekly schedule. Custom windows may cross midnight; overlapping windows are rejected. Custom schedules inherit General timezone, then workspace business-hours timezone, then America/Santiago. The optional legacy timezone field is accepted for compatibility and normalized by the server. Outside-business-hours mode requires configured workspace or tenant-owned team hours. The follow-through rules are optional: `human_silence_resume` (retired: accepted and stored for compatibility, it does nothing; use `after_handoff.ai_answers_waiting` on PUT /followups/recontacto) and `handoff_renotify` (after an AI handoff, re-notify the team every N minutes while the customer waits). Omitting either keeps the value already saved on the channel.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Channels"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "always",
                      "outside_business_hours",
                      "custom"
                    ]
                  },
                  "resume_human_conversations": {
                    "type": "boolean",
                    "description": "While the channel is available, let the AI answer inbound customer messages on conversations a human is handling. Only ever a reply to an incoming message: nothing is sent at window start and the inbox is never swept for unanswered threads (ADR 0102 §5)."
                  },
                  "business_hours_source": {
                    "type": "string",
                    "enum": [
                      "workspace",
                      "team"
                    ]
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "timezone": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "default": "America/Santiago"
                  },
                  "weekly_windows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "day": {
                          "type": "string",
                          "enum": [
                            "mon",
                            "tue",
                            "wed",
                            "thu",
                            "fri",
                            "sat",
                            "sun"
                          ]
                        },
                        "start": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                        },
                        "end": {
                          "type": "string",
                          "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                        }
                      },
                      "required": [
                        "day",
                        "start",
                        "end"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 100
                  },
                  "human_silence_resume": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "minutes": {
                        "type": "integer",
                        "minimum": 5,
                        "maximum": 240
                      }
                    },
                    "required": [
                      "enabled",
                      "minutes"
                    ],
                    "additionalProperties": false,
                    "description": "RETIRED (2026-10-25): a conversation a person handles is silent until «Devolver a la IA», so this switch no longer makes the AI answer. It is accepted and stored for compatibility only. Whether the AI answers a waiting customer is `after_handoff.ai_answers_waiting` on `/followups/recontacto`. Absent = enabled, 30 minutes."
                  },
                  "handoff_renotify": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "minutes": {
                        "type": "integer",
                        "minimum": 10,
                        "maximum": 240
                      }
                    },
                    "required": [
                      "enabled",
                      "minutes"
                    ],
                    "additionalProperties": false,
                    "description": "The AI handed the conversation off explicitly, so it stays out. While the customer's message stays unanswered, notify the assignee (or, when nobody is assigned, the channel roster) every `minutes`, with owners and admins copied. Checked on a 10-minute tick; stops 24 hours after the customer's message. Absent = DISABLED (opt-in per channel); once enabled, the default cadence is 30 minutes."
                  }
                },
                "required": [
                  "mode",
                  "resume_human_conversations",
                  "business_hours_source",
                  "team_id",
                  "weekly_windows"
                ],
                "additionalProperties": false
              },
              "example": {
                "mode": "outside_business_hours",
                "resume_human_conversations": false,
                "business_hours_source": "workspace",
                "team_id": null,
                "weekly_windows": [],
                "human_silence_resume": {
                  "enabled": true,
                  "minutes": 30
                },
                "handoff_renotify": {
                  "enabled": false,
                  "minutes": 30
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI availability policy and status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "policy": {
                      "mode": "outside_business_hours",
                      "resume_human_conversations": false,
                      "business_hours_source": "workspace",
                      "team_id": null,
                      "weekly_windows": [],
                      "human_silence_resume": {
                        "enabled": true,
                        "minutes": 30
                      },
                      "handoff_renotify": {
                        "enabled": false,
                        "minutes": 30
                      },
                      "timezone": "America/Santiago"
                    },
                    "status": {
                      "configured": true,
                      "available": false,
                      "reason": "inside_business_hours",
                      "window_start": "2026-09-15T12:00:00.000Z",
                      "window_end": "2026-09-15T22:00:00.000Z",
                      "next_transition": "2026-09-15T22:00:00.000Z",
                      "effective_timezone": "America/Santiago",
                      "policy_revision": "sha256:<revision>"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/me/notifications": {
      "get": {
        "summary": "In-app notification feed for the active workspace (keyset-paginated)",
        "description": "The caller’s own bell, newest first. Only the last 14 days are returned (older rows expire and are deleted after 30 days); `meta.unreadCount` counts unread items inside that same window. Every row carries a typed `target` — where a click leads: `record` (one subject), `list` (a filtered list, e.g. the Búsquedas a daily summary counted) or `page` (a fixed screen) — never a URL. A `live` row is a standing condition (a broken channel, the approvals queue) refreshed in place and marked read when it ends. Requires a workspace member (a session or a personal token acting as one); an `sk_*` API key gets 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Me"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 80
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Notification feed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Notification"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "nextCursor": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "nextCursor"
                          ]
                        },
                        "unreadCount": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "pagination",
                        "unreadCount"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "019a3b1c-2d4e-7f00-8a1b-2c3d4e5f6a70",
                      "type": "busqueda.daily_summary",
                      "category": "followups",
                      "actor_id": null,
                      "entity_type": "tenant",
                      "entity_id": "7b1f0c52-3e4a-4d8b-9c21-5a6b7c8d9e0f",
                      "data": {
                        "sent": 4,
                        "failed": 1,
                        "customers": [
                          "Camila Rojas",
                          "Diego Muñoz",
                          "Paula Soto"
                        ],
                        "from": "2026-10-07T12:00:00.000Z",
                        "to": "2026-10-08T12:00:00.000Z"
                      },
                      "target": {
                        "kind": "list",
                        "screen": "busquedas",
                        "filter": {
                          "notified_from": "2026-10-07T12:00:00.000Z",
                          "notified_to": "2026-10-08T12:00:00.000Z",
                          "status": "any"
                        }
                      },
                      "secondary_target": {
                        "kind": "list",
                        "screen": "busquedas",
                        "filter": {
                          "notified_from": "2026-10-07T12:00:00.000Z",
                          "notified_to": "2026-10-08T12:00:00.000Z",
                          "notice": "failed",
                          "status": "any"
                        }
                      },
                      "live": false,
                      "read_at": null,
                      "created_at": "2026-10-08T12:00:04.512Z"
                    },
                    {
                      "id": "019a3a77-90ab-7c00-9d1e-2f3a4b5c6d7e",
                      "type": "agenda_booked",
                      "category": "agenda",
                      "actor_id": null,
                      "entity_type": "appointment",
                      "entity_id": "c4d5e6f7-0819-4a2b-8c3d-4e5f60718293",
                      "data": {},
                      "target": {
                        "kind": "record",
                        "record": "appointment",
                        "id": "c4d5e6f7-0819-4a2b-8c3d-4e5f60718293"
                      },
                      "secondary_target": null,
                      "live": false,
                      "read_at": "2026-10-08T09:15:00.000Z",
                      "created_at": "2026-10-07T21:40:11.003Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "nextCursor": null
                    },
                    "unreadCount": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/me/notification-preferences": {
      "get": {
        "summary": "The caller’s notification preferences, generated from the registry",
        "description": "Every notification type this member can receive in this workspace (filtered by role and vertical), grouped by category, with the state of each channel — bell (`in_app`), `email`, `push`, `whatsapp`: `on`/`off` (their choice, or the default when they never chose), `locked` (mandatory, with `locked_reason`), `always` (the bell of a type they must act on) or `unavailable`. WhatsApp is available only for types the workspace armed for staff WhatsApp. `preset` is `important` (the defaults), `all` or `custom`. Requires a workspace member; an `sk_*` API key gets 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Me"
        ],
        "responses": {
          "200": {
            "description": "Preference catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NotificationPreferenceCatalog"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "preset": "important",
                    "push_enabled": true,
                    "categories": [
                      "inbox",
                      "agenda",
                      "sales",
                      "followups",
                      "approvals",
                      "payments",
                      "agents",
                      "integrations",
                      "safety",
                      "account"
                    ],
                    "types": [
                      {
                        "key": "assigned",
                        "category": "inbox",
                        "slug": "assigned",
                        "mandatory": false,
                        "locked_reason": null,
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "always",
                            "default": "on"
                          },
                          "email": {
                            "state": "off",
                            "default": "off"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "on",
                            "default": "workspace"
                          }
                        }
                      },
                      {
                        "key": "agenda_booked",
                        "category": "agenda",
                        "slug": "agenda_booked",
                        "mandatory": false,
                        "locked_reason": null,
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "always",
                            "default": "on"
                          },
                          "email": {
                            "state": "on",
                            "default": "on"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "unavailable",
                            "default": null
                          }
                        }
                      },
                      {
                        "key": "payment_review_needed",
                        "category": "payments",
                        "slug": "payment_review_needed",
                        "mandatory": true,
                        "locked_reason": "customer_waiting",
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "locked",
                            "default": "locked"
                          },
                          "email": {
                            "state": "on",
                            "default": "on"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "on",
                            "default": "workspace"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Change notification preferences (preset and/or single cells)",
        "description": "JSON-merged: only the cells named change, everything else is kept. `preset: important` returns to the defaults (clears every per-type choice); `all` switches every switchable cell on; any `changes` entry without a preset makes the preset `custom`. 400 for an unknown type, a channel the type does not use, a mandatory (locked) cell, or the bell of a type that always appears in the bell. Returns the fresh catalog.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Me"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotificationPreferencesUpdate"
              },
              "example": {
                "changes": [
                  {
                    "key": "agenda_booked",
                    "channel": "email",
                    "enabled": false
                  },
                  {
                    "key": "coach.finding_ready",
                    "channel": "in_app",
                    "enabled": false
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preference catalog after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NotificationPreferenceCatalog"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "preset": "important",
                    "push_enabled": true,
                    "categories": [
                      "inbox",
                      "agenda",
                      "sales",
                      "followups",
                      "approvals",
                      "payments",
                      "agents",
                      "integrations",
                      "safety",
                      "account"
                    ],
                    "types": [
                      {
                        "key": "assigned",
                        "category": "inbox",
                        "slug": "assigned",
                        "mandatory": false,
                        "locked_reason": null,
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "always",
                            "default": "on"
                          },
                          "email": {
                            "state": "off",
                            "default": "off"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "on",
                            "default": "workspace"
                          }
                        }
                      },
                      {
                        "key": "agenda_booked",
                        "category": "agenda",
                        "slug": "agenda_booked",
                        "mandatory": false,
                        "locked_reason": null,
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "always",
                            "default": "on"
                          },
                          "email": {
                            "state": "on",
                            "default": "on"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "unavailable",
                            "default": null
                          }
                        }
                      },
                      {
                        "key": "payment_review_needed",
                        "category": "payments",
                        "slug": "payment_review_needed",
                        "mandatory": true,
                        "locked_reason": "customer_waiting",
                        "bell_mutable": false,
                        "delivery": "immediate",
                        "channels": {
                          "in_app": {
                            "state": "locked",
                            "default": "locked"
                          },
                          "email": {
                            "state": "on",
                            "default": "on"
                          },
                          "push": {
                            "state": "on",
                            "default": "on"
                          },
                          "whatsapp": {
                            "state": "on",
                            "default": "workspace"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tenant/settings": {
      "get": {
        "summary": "Read tenant-level settings",
        "description": "The stored settings object, as saved through `PUT /tenant/settings`. Among its keys, `ads` holds the Vitrina Ads wizard choice: `{ tag_choice: \"site\" | \"no_site\" | null, tag_choice_at: string | null }` (`tag_choice_at` is stamped by the server on every write); absent when the workspace never answered the «Instalar el tag» step. Only the keys documented here are part of the API pública: the workspace's general identity (`name`, `timezone`, `language`, `currency`, `date_format`, `website`) and the Vitrina Ads choices under `ads`, the template voice under `voice`, the AI agent's banned terms under `agent_language`, and the notification rails under `notifications` (arming a staff WhatsApp rail submits its template to Meta and wires it on approval; the read-only `staff_rails` reports each rail template state). Every API credential (API key, personal token, connected app, and the MCP) receives exactly those keys. Secrets are write-only: `slack_webhook_url` is never returned — a read answers `••••` plus `slack_webhook_configured`. Connected apps write only `ads`, `voice`, `agent_language` and `notifications`: any other key, `slack_webhook_url` included, is refused with 403, so a connected app cannot send the mask back either. For a workspace's own API key or personal token with `tenant:write`, writing `••••` back keeps the stored webhook, a new URL replaces it, and `null` or an empty string clears it. The webhook receives only the workspace's own alerts (pricing alerts and digests, reminders about its listings, consignments, credit applications and post-sale obligations, SLA breaches and safety holds); Vitrina never posts its internal alerts to it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tenant"
        ],
        "responses": {
          "200": {
            "description": "Tenant settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TenantSettingsPublic"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "name": "Automotora Demo",
                    "timezone": "America/Santiago",
                    "language": "es",
                    "currency": "CLP",
                    "website": "https://www.automotora-demo.cl",
                    "ads": {
                      "tag_choice": "site",
                      "tag_choice_at": "2026-09-23T15:00:00.000Z",
                      "goal": {
                        "kind": "outcomes",
                        "target": 30,
                        "period": "month"
                      },
                      "goal_at": "2026-09-01T12:00:00.000Z",
                      "goal_by": "20000000-0000-4000-8000-000000000001"
                    },
                    "slack_webhook_configured": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Patch tenant-level settings",
        "description": "Shallow-merges the body into the stored settings and answers the result. `ads` is merged key by key (a write that sends only `ads.tag_choice` keeps `ads.goal`), and the server stamps `tag_choice_at` / `goal_at` / `goal_by` — a client never sends them (400). A connected app may write only `ads`; any other key is refused with 403 and nothing is written. Requires `tenant:write`, or `ads:write` for a body that carries only `ads`. Only the keys documented here are part of the API pública: the workspace's general identity (`name`, `timezone`, `language`, `currency`, `date_format`, `website`) and the Vitrina Ads choices under `ads`, the template voice under `voice`, the AI agent's banned terms under `agent_language`, and the notification rails under `notifications` (arming a staff WhatsApp rail submits its template to Meta and wires it on approval; the read-only `staff_rails` reports each rail template state). Every API credential (API key, personal token, connected app, and the MCP) receives exactly those keys. Secrets are write-only: `slack_webhook_url` is never returned — a read answers `••••` plus `slack_webhook_configured`. Connected apps write only `ads`, `voice`, `agent_language` and `notifications`: any other key, `slack_webhook_url` included, is refused with 403, so a connected app cannot send the mask back either. For a workspace's own API key or personal token with `tenant:write`, writing `••••` back keeps the stored webhook, a new URL replaces it, and `null` or an empty string clears it. The webhook receives only the workspace's own alerts (pricing alerts and digests, reminders about its listings, consignments, credit applications and post-sale obligations, SLA breaches and safety holds); Vitrina never posts its internal alerts to it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tenant"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TenantSettingsPublicWrite"
              },
              "example": {
                "ads": {
                  "tag_choice": "site"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tenant settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TenantSettingsPublic"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "name": "Automotora Demo",
                    "timezone": "America/Santiago",
                    "language": "es",
                    "currency": "CLP",
                    "website": "https://www.automotora-demo.cl",
                    "ads": {
                      "tag_choice": "site",
                      "tag_choice_at": "2026-09-24T12:00:00.000Z"
                    },
                    "slack_webhook_configured": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tenant/modules": {
      "get": {
        "summary": "Read the workspace modules (Proyectos / Corretaje)",
        "description": "The halves of its vertical the workspace runs. Real estate offers `projects` (Módulo Proyectos, an inmobiliaria) and `brokerage` (Módulo Corretaje, a corredora); a workspace may run both. `modules` is the EFFECTIVE set: a real-estate workspace that never chose reads as `[\"brokerage\"]`, and a vertical without modules reads as `[]`. A module-specific route refuses a workspace without its module with 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tenant"
        ],
        "responses": {
          "200": {
            "description": "Workspace modules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "vertical": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "modules": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "projects",
                              "brokerage"
                            ],
                            "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                          },
                          "description": "The EFFECTIVE set. A real-estate workspace that never chose reads as `[\"brokerage\"]`; a vertical without modules reads as `[]`."
                        },
                        "available": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "projects",
                              "brokerage"
                            ],
                            "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                          },
                          "description": "The modules this workspace’s vertical offers."
                        }
                      },
                      "required": [
                        "vertical",
                        "modules",
                        "available"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vertical": "real-estate",
                    "modules": [
                      "brokerage"
                    ],
                    "available": [
                      "projects",
                      "brokerage"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the workspace modules (Proyectos / Corretaje)",
        "description": "Replaces the module set (at least one; only modules the vertical offers — 400 otherwise) and brings the boards in line: a module turned on gets its board (`projects` → «Proyectos», Lead → Visita agendada → Visita realizada → Cotización → Reserva → Promesa → Escritura → Entrega, with Desistimiento as the lost stage; `brokerage` → «Corretaje»), reported in `pipelines.added`. A module turned off loses its board only while the board is empty (`pipelines.removed`); a board holding leads is kept (`pipelines.in_use`). When the sales fallback sat on a module turned off it moves to a module still on (`pipelines.fallback_moved_to`). Idempotent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tenant"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "modules": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "projects",
                        "brokerage"
                      ],
                      "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                    },
                    "minItems": 1,
                    "description": "The full set to run. At least one; both may be on."
                  }
                },
                "required": [
                  "modules"
                ]
              },
              "example": {
                "modules": [
                  "projects"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Workspace modules + what happened to the boards",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "vertical": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "modules": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "projects",
                              "brokerage"
                            ],
                            "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                          },
                          "description": "The EFFECTIVE set. A real-estate workspace that never chose reads as `[\"brokerage\"]`; a vertical without modules reads as `[]`."
                        },
                        "available": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "projects",
                              "brokerage"
                            ],
                            "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                          },
                          "description": "The modules this workspace’s vertical offers."
                        },
                        "pipelines": {
                          "type": "object",
                          "properties": {
                            "added": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "module": {
                                    "type": "string",
                                    "enum": [
                                      "projects",
                                      "brokerage"
                                    ],
                                    "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                                  },
                                  "template_key": {
                                    "type": "string"
                                  },
                                  "pipeline_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  }
                                },
                                "required": [
                                  "module",
                                  "template_key",
                                  "pipeline_id"
                                ]
                              },
                              "description": "Boards of a module just turned on, created now."
                            },
                            "removed": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "module": {
                                    "type": "string",
                                    "enum": [
                                      "projects",
                                      "brokerage"
                                    ],
                                    "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                                  },
                                  "template_key": {
                                    "type": "string"
                                  },
                                  "pipeline_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  }
                                },
                                "required": [
                                  "module",
                                  "template_key",
                                  "pipeline_id"
                                ]
                              },
                              "description": "Empty boards of a module turned off, removed."
                            },
                            "in_use": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "module": {
                                    "type": "string",
                                    "enum": [
                                      "projects",
                                      "brokerage"
                                    ],
                                    "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                                  },
                                  "template_key": {
                                    "type": "string"
                                  },
                                  "pipeline_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  }
                                },
                                "required": [
                                  "module",
                                  "template_key",
                                  "pipeline_id"
                                ]
                              },
                              "description": "Boards of a module turned off that hold leads or tickets, kept untouched."
                            },
                            "fallback_moved_to": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "module": {
                                  "type": "string",
                                  "enum": [
                                    "projects",
                                    "brokerage"
                                  ],
                                  "description": "`projects` = Módulo Proyectos (an inmobiliaria selling its own proyectos); `brokerage` = Módulo Corretaje (a corredora selling or renting third-party properties)."
                                },
                                "template_key": {
                                  "type": "string"
                                },
                                "pipeline_id": {
                                  "type": "string",
                                  "format": "uuid"
                                }
                              },
                              "required": [
                                "module",
                                "template_key",
                                "pipeline_id"
                              ],
                              "description": "Where the sales fallback board moved, when it sat on a module turned off."
                            }
                          },
                          "required": [
                            "added",
                            "removed",
                            "in_use",
                            "fallback_moved_to"
                          ]
                        }
                      },
                      "required": [
                        "vertical",
                        "modules",
                        "available",
                        "pipelines"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vertical": "real-estate",
                    "modules": [
                      "projects"
                    ],
                    "available": [
                      "projects",
                      "brokerage"
                    ],
                    "pipelines": {
                      "added": [
                        {
                          "module": "projects",
                          "template_key": "real_estate_developer_sales",
                          "pipeline_id": "6f1c2b8e-3d4a-4b5c-9e7f-1a2b3c4d5e6f"
                        }
                      ],
                      "removed": [
                        {
                          "module": "brokerage",
                          "template_key": "real_estate_sales",
                          "pipeline_id": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d"
                        }
                      ],
                      "in_use": [],
                      "fallback_moved_to": {
                        "module": "projects",
                        "template_key": "real_estate_developer_sales",
                        "pipeline_id": "6f1c2b8e-3d4a-4b5c-9e7f-1a2b3c4d5e6f"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/entitlements": {
      "get": {
        "summary": "List the add-on catalog, this workspace's entitlements, and live usage",
        "description": "The Complementos hub read. `catalog` is the typed feature catalog (prices are PR-reviewed code, never a runtime SKU table); `entitlements` is one row per add-on this workspace has ever held; `usage` carries the live meter for each metered add-on over the current **Santiago** month — `usage.tasador` is `{ used, free_allowance, period }`, where `used` is aggregated on read from `usage_event` (never a counter) and `free_allowance` reflects any per-workspace override, so it is the same pair of numbers the access gate decides on. Each entitlement carries `billed_by`: `vitrina` when Vitrina invoices the add-on, any other value when another party covers it (Vitrina charges nothing, so a client shows it as included rather than at the catalog price). A row may also carry a time-boxed waiver: `waiver_until` is the last free calendar day (America/Santiago) and `waiver_active` says whether it covers the add-on right now; once it lapses the add-on bills normally with no further action. `complimentary` and `covered_by` say when the add-on carries no price on the invoice at all (granted free, or covered by the workspace's fixed contract / not-billed contract): a client then shows it as included and never the catalog price. `offers` says, per add-on, whether THIS workspace can buy it (`available`) and if not why (`sandbox`: a demonstration workspace; `vertical`: not sold to this type of business) — the same rule the activate route enforces with a 409 `ADDON_NOT_OFFERED`. `activators` names the active owners and admins a member without `tenant:write` can ask to activate one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Entitlements"
        ],
        "responses": {
          "200": {
            "description": "Catalog, the workspace's entitlements, and live usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "catalog": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "access": {
                                "type": "string",
                                "enum": [
                                  "entitlement",
                                  "quota"
                                ]
                              },
                              "flatUfMonth": {
                                "type": "number"
                              },
                              "flatClpMonth": {
                                "type": "number"
                              },
                              "freeAllowance": {
                                "type": "number"
                              },
                              "unitUf": {
                                "type": "number"
                              },
                              "meter": {
                                "type": "string"
                              },
                              "perUnitClpMonth": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "billedUnit": {
                                "type": "string"
                              },
                              "verticals": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The business types (`tenant.vertical`) this add-on is sold to. Omitted = every vertical. Read `offers` for the per-workspace answer."
                              }
                            },
                            "required": [
                              "key",
                              "access"
                            ]
                          }
                        },
                        "monthly_charge": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp_net": {
                              "type": "integer"
                            },
                            "iva_amount": {
                              "type": "integer"
                            },
                            "clp_total": {
                              "type": "integer"
                            },
                            "uf_estimate": {
                              "type": "boolean",
                              "description": "A UF-priced fee was converted at today's UF: an estimate."
                            }
                          },
                          "required": [
                            "clp_net",
                            "iva_amount",
                            "clp_total",
                            "uf_estimate"
                          ],
                          "description": "What the active, Vitrina-billed add-ons add to the monthly cobro: neto, IVA = round(neto × 19 %), total. Null when none."
                        },
                        "entitlements": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "tenant_id": {
                                "type": "string"
                              },
                              "feature": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "active",
                                  "canceled",
                                  "suspended"
                                ]
                              },
                              "source": {
                                "type": "string",
                                "enum": [
                                  "self_serve",
                                  "billing",
                                  "admin"
                                ]
                              },
                              "granted_at": {
                                "type": "string"
                              },
                              "cancel_at_period_end": {
                                "type": "boolean"
                              },
                              "canceled_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "metadata": {
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "waiver_until": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Last calendar day (`YYYY-MM-DD`, America/Santiago) this add-on is free under a time-boxed waiver: no charge accrues through the end of that day and activation needs no payment method. It bills normally from 00:00 Santiago of the next day. `null` = no waiver."
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "updated_at": {
                                "type": "string"
                              },
                              "billed_by": {
                                "type": "string",
                                "description": "Who invoices this add-on. `vitrina` (default) = Vitrina bills it at the catalog price. Any other value names the partner whose plan covers it; Vitrina charges nothing, so show the add-on as included and never render the partner value or the catalog price."
                              },
                              "waiver_active": {
                                "type": "boolean",
                                "description": "True while the `waiver_until` waiver covers the add-on right now (resolved server-side in America/Santiago). While true, show the add-on as included until that date instead of the catalog price."
                              },
                              "complimentary": {
                                "type": "boolean",
                                "description": "Granted free of charge by a founder decision: Vitrina never bills this add-on. Show «Incluido en tu plan», never the catalog price."
                              },
                              "covered_by": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "complimentary",
                                  "contract",
                                  "not_billed"
                                ],
                                "description": "Why this add-on carries no price on the invoice. `complimentary`: granted free; `contract`: the workspace has a fixed monthly contract that replaces every add-on line; `not_billed`: the workspace is never billed. `null` = it bills at the catalog price (unless `billed_by` or `waiver_active` says otherwise). When non-null, show «Incluido en tu plan» instead of the price."
                              }
                            },
                            "required": [
                              "tenant_id",
                              "feature",
                              "status",
                              "source",
                              "granted_at",
                              "cancel_at_period_end",
                              "canceled_at",
                              "metadata",
                              "created_at",
                              "updated_at",
                              "billed_by",
                              "waiver_active",
                              "complimentary",
                              "covered_by"
                            ]
                          }
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "tasador": {
                              "type": "object",
                              "properties": {
                                "used": {
                                  "type": "number"
                                },
                                "free_allowance": {
                                  "type": "number"
                                },
                                "period": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "used",
                                "free_allowance",
                                "period"
                              ]
                            }
                          },
                          "required": [
                            "tasador"
                          ]
                        },
                        "offers": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "object",
                            "properties": {
                              "available": {
                                "type": "boolean",
                                "description": "True when `POST /entitlements/{feature}/activate` would accept this add-on for this workspace (a payment method may still be needed)."
                              },
                              "reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "sandbox",
                                  "vertical"
                                ],
                                "description": "`null` when available. `sandbox`: a demonstration workspace never buys add-ons. `vertical`: the add-on is not sold to this type of business (see the catalog entry `verticals`)."
                              }
                            },
                            "required": [
                              "available",
                              "reason"
                            ]
                          },
                          "description": "The offer for every add-on in `catalog`, keyed by its `key`."
                        },
                        "activators": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "user_id": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string",
                                "description": "The member's display name."
                              }
                            },
                            "required": [
                              "user_id",
                              "name"
                            ]
                          },
                          "description": "Active owners and admins with a display name: who a member without `tenant:write` should ask to activate an add-on. Can be empty."
                        }
                      },
                      "required": [
                        "catalog",
                        "monthly_charge",
                        "entitlements",
                        "usage",
                        "offers",
                        "activators"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "catalog": [
                      {
                        "key": "vitrina_ads",
                        "access": "entitlement",
                        "flatClpMonth": 99000
                      },
                      {
                        "key": "tasador",
                        "access": "quota",
                        "flatUfMonth": 0.5,
                        "freeAllowance": 10,
                        "meter": "tasador_valuation"
                      }
                    ],
                    "entitlements": [
                      {
                        "tenant_id": "11111111-1111-4111-8111-111111111111",
                        "feature": "vitrina_ads",
                        "status": "active",
                        "source": "self_serve",
                        "granted_at": "2026-09-01T12:00:00.000Z",
                        "cancel_at_period_end": false,
                        "canceled_at": null,
                        "metadata": {},
                        "waiver_until": "2026-10-31",
                        "created_at": "2026-09-01T12:00:00.000Z",
                        "updated_at": "2026-09-01T12:00:00.000Z",
                        "billed_by": "vitrina",
                        "waiver_active": true,
                        "complimentary": false,
                        "covered_by": null
                      }
                    ],
                    "usage": {
                      "tasador": {
                        "used": 3,
                        "free_allowance": 10,
                        "period": "2026-09"
                      }
                    },
                    "offers": {
                      "vitrina_ads": {
                        "available": true,
                        "reason": null
                      },
                      "tasador": {
                        "available": true,
                        "reason": null
                      }
                    },
                    "activators": [
                      {
                        "user_id": "22222222-2222-4222-8222-222222222222",
                        "name": "Ana Rojas"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/entitlements/{feature}/activate": {
      "post": {
        "summary": "Self-serve activate an add-on (returns needs_enrollment without a payment mandate)",
        "description": "Grants `feature` to the workspace once an active payment mandate exists (`{ status: \"active\", entitlement }`); with none, answers `{ status: \"needs_enrollment\" }` and grants nothing — the caller routes the dealer through the Fintoc/Mercado Pago enrollment step and retries. No payment method is needed when the add-on costs the workspace nothing: its row is covered by another biller (`billed_by` other than `vitrina`) or sits inside an active `waiver_until` waiver; such an activation answers `active` and accrues no charge for the covered days. An add-on this workspace cannot buy answers 409 `ADDON_NOT_OFFERED` (`details.reason`: `sandbox` or `vertical`, the same answer `GET /entitlements` publishes in `offers`); reactivating an add-on the workspace still holds is never refused for that. Idempotent on an already-active feature (re-answers the same `active` row rather than erroring).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Entitlements"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "required": true,
            "name": "feature",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Activation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "active",
                    "entitlement": {
                      "tenant_id": "11111111-1111-4111-8111-111111111111",
                      "feature": "vitrina_ads",
                      "status": "active",
                      "source": "self_serve",
                      "granted_at": "2026-09-23T15:00:00.000Z",
                      "cancel_at_period_end": false,
                      "canceled_at": null,
                      "metadata": {},
                      "waiver_until": null,
                      "created_at": "2026-09-23T15:00:00.000Z",
                      "updated_at": "2026-09-23T15:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/entitlements/{feature}/cancel": {
      "post": {
        "summary": "Cancel an add-on at period end",
        "description": "Flags the workspace’s `feature` row `cancel_at_period_end: true` — the add-on stays active through the period already paid for and turns off at rollover, never immediately. 404 when the workspace holds no entitlement for `feature` to cancel.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Entitlements"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "required": true,
            "name": "feature",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancellation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "canceling",
                    "entitlement": {
                      "tenant_id": "11111111-1111-4111-8111-111111111111",
                      "feature": "vitrina_ads",
                      "status": "active",
                      "source": "self_serve",
                      "granted_at": "2026-09-01T12:00:00.000Z",
                      "cancel_at_period_end": true,
                      "canceled_at": "2026-09-23T15:00:00.000Z",
                      "metadata": {},
                      "created_at": "2026-09-01T12:00:00.000Z",
                      "updated_at": "2026-09-23T15:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/catalog/brands": {
      "get": {
        "summary": "List car makes (catalog)",
        "description": "Every car make Vitrina recognises, one entry per make in its canonical spelling (the one Chileautos uses: \"Mercedes-Benz\", \"SsangYong\", \"Citroën\"). Only cars, SUVs, pickups and light commercial vehicles: no motorcycles, boats, machinery or accessories. Sources: the Chilean homologación catalog and the labels Chileautos publishes. Use it to normalize a vehicle's make / model / version BEFORE you create or update it: send the value exactly as this list spells it and Vitrina (and the portals it publishes to, Chileautos included) classifies the car. Matching is case-, accent-, space- and hyphen-insensitive (\"mercedes benz\" = \"Mercedes-Benz\", \"rav 4\" = \"RAV4\"). Global reference data (the same for every workspace), cached for an hour. Readable with `marketplace:read` or `stock:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "Brands",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    "Chevrolet",
                    "Mercedes-Benz",
                    "SsangYong",
                    "Toyota"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/catalog/models": {
      "get": {
        "summary": "List the models of a make (catalog)",
        "description": "The models of `brand` (any spelling of the make), one entry per model: spelling variants are collapsed (\"4 Runner\" / \"4runner\" → \"4Runner\") and no entry carries a version, engine or body description. Unknown make → empty list. Use it to normalize a vehicle's make / model / version BEFORE you create or update it: send the value exactly as this list spells it and Vitrina (and the portals it publishes to, Chileautos included) classifies the car. Matching is case-, accent-, space- and hyphen-insensitive (\"mercedes benz\" = \"Mercedes-Benz\", \"rav 4\" = \"RAV4\"). Global reference data (the same for every workspace), cached for an hour. Readable with `marketplace:read` or `stock:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "brand",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Models",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    "4Runner",
                    "Corolla",
                    "Corolla Cross",
                    "Hilux",
                    "RAV4",
                    "Yaris"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/catalog/versions": {
      "get": {
        "summary": "List the versions of a make + model (catalog)",
        "description": "Known versions (trims) of `brand` + `model`, in trim style: displacement, trim level, drivetrain and gearbox (\"2.0 LE 4X2 MT\", \"2.5 HEV AWD\"), deduplicated. A version is matched approximately (word overlap, same displacement), so it is a guide rather than a closed list: a version that is not here is still accepted. Use it to normalize a vehicle's make / model / version BEFORE you create or update it: send the value exactly as this list spells it and Vitrina (and the portals it publishes to, Chileautos included) classifies the car. Matching is case-, accent-, space- and hyphen-insensitive (\"mercedes benz\" = \"Mercedes-Benz\", \"rav 4\" = \"RAV4\"). Global reference data (the same for every workspace), cached for an hour. Readable with `marketplace:read` or `stock:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "brand",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "model",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    "2.0 4X2 CVT",
                    "2.0 LE 4X2 MT",
                    "2.5 AWD HEV",
                    "2.5 XLE 4X4 AT"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/field-visibility": {
      "get": {
        "summary": "Where each vehicle field publishes (the editor’s marker source)",
        "description": "Every `vehicle` column — plus the allow-listed `attributes.*` keys the editor renders as their own controls — classified against the three outbound audiences the backend enforces: `public_listing` (the dealer website, the ficha técnica, the widget card), `portal_publish` (Mercado Libre / Chileautos / Yapo) and `agent_context` (the AI agent). Each row carries the raw `audiences` list AND a server-derived `visibility` rollup — `public` | `portal_only` | `internal` — so a client never re-implements the decision. THREE states, not two: `registration_number` is withheld from the public listing and REQUIRED by the portals, so a binary public/internal marker would have to lie about it.\n\n`audiences[]` echoes how each audience reaches THIS tenant. `public_listing.detail` is `sitio_publicado` | `sitio_no_publicado` | `sin_complemento`; note `reaches` stays true in all three, because that audience still feeds the ficha técnica and the widget card when the dealer website is unpublished or the `dealer_site` add-on is absent — only the copy softens.\n\nREAD-ONLY. Which fields publish is a compiled registry, not tenant data: there is no write counterpart, and a per-tenant override is the rejected alternative in ADR 0068. The editable per-field control is the ADR-0023 candado (`/vehicles/{id}/fields/protect|release`), which answers \"who may WRITE it\" rather than \"who may SEE it\".",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "The field-visibility registry for this tenant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "audiences": [
                      {
                        "key": "public_listing",
                        "label": "Sitio web y ficha técnica",
                        "reaches": true,
                        "detail": "sitio_publicado"
                      },
                      {
                        "key": "portal_publish",
                        "label": "Portales",
                        "reaches": true,
                        "detail": null
                      },
                      {
                        "key": "agent_context",
                        "label": "Agente de IA",
                        "reaches": true,
                        "detail": null
                      }
                    ],
                    "fields": [
                      {
                        "field": "make",
                        "audiences": [
                          "public_listing",
                          "portal_publish",
                          "agent_context"
                        ],
                        "visibility": "public"
                      },
                      {
                        "field": "registration_number",
                        "audiences": [
                          "portal_publish"
                        ],
                        "visibility": "portal_only"
                      },
                      {
                        "field": "tenant_id",
                        "audiences": [],
                        "visibility": "internal"
                      }
                    ]
                  },
                  "meta": {
                    "total": 78
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/photo-labels": {
      "get": {
        "summary": "What each stock photo shows (exterior, interior, motor…)",
        "description": "Labels for the photos of one vehicle (`vehicle_id`) and/or explicit `url`s (repeat the param, up to 40). A url with no row has not been labelled yet. `source` is `ai` (the vision model) or `manual` (a person corrected it; the AI never overwrites it). `status: failed` = the last vision call failed and the label is unknown.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 2048
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "maxItems": 40
                }
              ]
            },
            "required": false,
            "name": "url",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Labels",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "labels": [
                    {
                      "url": "https://media.vitrinadev.com/vehicles/00000000-0000-4000-8000-000000000001/8a76.jpg",
                      "label": "Interior — tablero y volante",
                      "view": "interior",
                      "source": "ai",
                      "status": "labelled"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Correct what a stock photo shows",
        "description": "Sets the view (and an optional short description) of one photo as a person's correction (`source: manual`). The AI agent uses it at once (get_vehicle `shows`, send_attachment view check) and no later AI pass overwrites it. The url must be one of this workspace's uploads, or a photo of `vehicle_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "view": {
                    "type": "string",
                    "enum": [
                      "exterior",
                      "interior",
                      "motor",
                      "maletero",
                      "detalle"
                    ]
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "url",
                  "view"
                ],
                "additionalProperties": false
              },
              "example": {
                "url": "https://media.vitrinadev.com/vehicles/00000000-0000-4000-8000-000000000001/8a76.jpg",
                "view": "interior",
                "description": "tablero y volante",
                "vehicle_id": "9f4a2b7c-1111-4a2b-8c3d-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved label",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "url": "https://media.vitrinadev.com/vehicles/00000000-0000-4000-8000-000000000001/8a76.jpg",
                  "label": "Interior — tablero y volante",
                  "view": "interior",
                  "source": "manual",
                  "status": "labelled"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/photo-labels/label": {
      "post": {
        "summary": "Label new uploads now",
        "description": "Queues the vision model for up to 30 of this workspace's uploaded photo urls that have no label yet (2–3 s each, 5 in parallel). Poll `GET /vehicles/photo-labels?url=…` for the result. Urls that are not this workspace's uploads are ignored.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "urls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 2048
                    },
                    "minItems": 1,
                    "maxItems": 30
                  }
                },
                "required": [
                  "urls"
                ],
                "additionalProperties": false
              },
              "example": {
                "urls": [
                  "https://media.vitrinadev.com/vehicles/00000000-0000-4000-8000-000000000001/8a76.jpg"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "queued": 1
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles": {
      "get": {
        "summary": "Search the tenant vehicle stock (filters + free text)",
        "description": "FIELD-LEVEL COST OMISSION (ERP S3-6): the per-unit money position — `floor_price_clp` (precio piso) — is withheld unless the caller holds `dealership_economics:read`. The key is ABSENT from the response object, not null: a caller who checks `'floor_price_clp' in body` sees it was never offered. `price_clp` (the asking price) is unaffected — it is the one money figure about a unit that is meant to be seen. The scope is NOT an alternative to `marketplace:read` (requireScope ORs its varargs, which would widen this route rather than narrow the field); it is an addition.\n\nTENENCIA (ADR 0025). `tenencia` filters the vehicle-level discriminator (`propio` | `consignacion`); `consignacion_modalidad` narrows a consignación by its ACTIVE contract — `en_local`, `virtual` (the car stays with its dueño) or `sin_contrato` (consigned with no contract in `estado=activo` on file). `consignacion_modalidad` is REFUSED with 400 unless `tenencia=consignacion` came with it: it names a kind of consignación, so it is only answerable about one.\n\nEvery row carries `tenencia` and `not_on_lot` — the latter true iff an ACTIVE contract with `modalidad=virtual` exists, resolved in ONE batched query for the page (never per row). `not_on_lot` is the flag a test-drive or walking-sheet surface acts on; `tenencia` alone cannot answer it, because the modalidad lives on the contract.\n\nPOSIBLE DUPLICADO. Every row carries `possible_duplicate`: true when an open duplicate candidate names the car (same photos and specs as another car, plates one character apart or an implausible VIN). The pairs are at `GET /vehicles/{id}/duplicate-candidates`.\n\n⚠ `tenencia=propio` IS THE COLUMN DEFAULT, not a recorded decision: that cohort includes every unit nobody has classified yet. Do not word it as \"comprado\".\n\nPhase 2 (tenencia_source): `tenencia_source` COMPOSES with `tenencia` rather than narrowing it — `?tenencia=propio&tenencia_source=decided` is \"propio, and a person or a document actually settled that\" (`decided` ⇒ `tenencia_source <> 'default'`); `document` / `declared` / `default` match the column exactly. See `POST /vehicles/tenencia` for how a `declared` row is produced.\n\nSIN COSTO (ADR 0114 §8). A car that entered without a compra (nota de compra), a consignación (contrato) or a parte de pago (nota de venta) has no acquisition, so nothing records what it cost. Every row carries `sin_costo: true` for such a unit (any status), and `view=sin_costo` lists the pile still on the floor — active, not vendido, no acquisition; the SAME predicate as the `sin_costo` count on `GET /vehicles/stats` and the `stock_without_cost` dealer alert. It is never a gate: bulk imports and migrations land cars here on purpose. `sin_costo` is a statement about the cost basis and follows the cost omission above: ABSENT without `dealership_economics:read`.\n\nMANDATO SIN FIRMAR (ADR 0114 §5). `view=mandato_sin_firmar` lists the cars whose consignación (activa or vendida) has an emitted mandato and no signed copy filed in the expediente — the SAME predicate as the `mandato_sin_firmar` count on `GET /vehicles/stats`. Never a gate, and no longer a dealer alert (retired 2026-11-10: it is a filter you open, not a to-do).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "make",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ]
            },
            "required": false,
            "name": "min_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ]
            },
            "required": false,
            "name": "max_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ]
            },
            "required": false,
            "name": "year_from",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ]
            },
            "required": false,
            "name": "year_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "description": "Kilometraje desde: only units whose odometer reading is at least this many km (inclusive). Units with no odometer reading are excluded when a bound is set."
            },
            "required": false,
            "name": "min_km",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "description": "Kilometraje hasta: only units whose odometer reading is at most this many km (inclusive)."
            },
            "required": false,
            "name": "max_km",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "transmission",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "fuel_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "body_style",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "color",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "disponible",
                "reservado",
                "vendido"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "camion",
                "maquinaria",
                "nautico"
              ]
            },
            "required": false,
            "name": "vehicle_type",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "has_error",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "needs_review",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "sin_estacionamiento",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "sin_sucursal",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean",
              "description": "«Papeles vencidos»: only cars in stock (active, not vendido) holding a paper — SOAP, permiso de circulación, revisión técnica or a named paper — whose «Vence» day has passed and that no later paper of the same kind replaces. Equals `papers_expired` on `GET /vehicles/stats`."
            },
            "required": false,
            "name": "papers_expired",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean",
              "description": "«Papeles por vencer»: only cars in stock holding a paper whose «Vence» day is today or within the next 30 days. Equals `papers_expiring` on `GET /vehicles/stats`."
            },
            "required": false,
            "name": "papers_expiring",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "propio",
                "consignacion"
              ]
            },
            "required": false,
            "name": "tenencia",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "en_local",
                "virtual",
                "sin_contrato"
              ]
            },
            "required": false,
            "name": "consignacion_modalidad",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "document",
                "declared",
                "default",
                "decided"
              ]
            },
            "required": false,
            "name": "tenencia_source",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "without_acquisition",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "published",
                "unpublished",
                "error",
                "stale",
                "source_deactivated",
                "sin_costo",
                "mandato_sin_firmar"
              ]
            },
            "required": false,
            "name": "view",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "price_asc",
                "price_desc",
                "year_desc",
                "year_asc",
                "km_asc",
                "km_desc",
                "recent",
                "days_desc",
                "interest_desc",
                "estacionamiento"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Vehicles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "origin_integration_id": null,
                      "source": "manual",
                      "make": "Toyota",
                      "model": "Corolla",
                      "version": "1.8 XEI CVT",
                      "year": 2022,
                      "title": null,
                      "short_title": null,
                      "price_amount": 13990000,
                      "price_clp": 13990000,
                      "watch_enabled": false,
                      "floor_price_clp": null,
                      "target_position": null,
                      "abono_amount_clp": 300000,
                      "merged_into": null,
                      "deleted_at": null,
                      "price_currency": "CLP",
                      "odometer_value": 42500,
                      "odometer_unit": "KM",
                      "fuel_type": "Bencina",
                      "gear_type": "Automática",
                      "body_style": "Sedán",
                      "color": "Gris",
                      "doors": 4,
                      "registration_number": "FGXY21",
                      "vin": "9BWZZZ377NP004512",
                      "plate_normalized": "FGXY21",
                      "listing_type": "Usado",
                      "record_type": null,
                      "type": "Car",
                      "merch_label": null,
                      "featured_at": null,
                      "vehicle_type": "auto",
                      "sale_status": null,
                      "status": "disponible",
                      "reserved_at": null,
                      "reserved_by_name": null,
                      "sold_at": null,
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "estacionamiento": null,
                      "description": "Mantenimientos al día en concesionario.",
                      "photos": [
                        {
                          "url": "https://images.example.cl/corolla-2022-1.jpg",
                          "order": 0
                        }
                      ],
                      "attributes": {},
                      "tags": [],
                      "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                      "active": true,
                      "protected_fields": [],
                      "deactivated_by": null,
                      "tenencia": "propio",
                      "tenencia_source": "default",
                      "tenencia_declared_by": null,
                      "tenencia_declared_at": null,
                      "iva_regime": null,
                      "keys_count": null,
                      "municipalidad_comuna_code": null,
                      "last_service_date": null,
                      "service_odometer_km": null,
                      "published_at": null,
                      "last_seen_at": null,
                      "created_at": "2026-09-15T18:34:48.575Z",
                      "updated_at": "2026-09-15T18:34:48.575Z",
                      "live_sale_note": null,
                      "active_reservation": null,
                      "sin_costo": true,
                      "not_on_lot": false
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 20,
                      "offset": 0
                    },
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a vehicle (manual stock)",
        "description": "plate_normalized is server-derived from registration_number; a plate already claimed by another ACTIVE vehicle of the tenant returns 409 (conflicto de patente).\n\n**Plate format.** For a road-registered type (`auto`, `camion`) a non-empty registration_number must be a Chilean plate: AB1234 (old series), BCDF12 (new series: consonants only, no M/N/Q), or for motorcycles AB123 / BCD12; separators, case and a trailing registry check digit are tolerated. Anything else is a 400 VALIDATION_ERROR with `details.reason = \"plate_format_invalid\"` and a Spanish `fieldErrors.registration_number`. An empty value and a pending declaration («en trámite») stay legal; `maquinaria`/`nautico` are not checked. Stock syncs from portals and feeds do not pass through this check.\n\n**Publish in the same call (`publish_to`).** Optional. Name the marketplaces this vehicle should go to — portal slugs such as `chileautos` / `mercadolibre`, or integration ids from GET /marketplaces — and it is published right after it is saved, through the same path as POST /vehicles/{id}/publish. Omitted or `[]` saves only; there is no default. Every entry is validated first: a portal with no connected account, a slug matching several accounts, or an id from another workspace answers **422 and nothing is saved**. Once saved, the vehicle stays saved: a portal that fails to publish is an `error` entry in `publications`, never a failed request (still 201). The response then carries `publications`, one `PublishOutcome` per account (`published`, `pending` for queued transports such as `chileautos_panel`, `already_published`, or `error` with the reason). `publications` is absent when `publish_to` was not sent.\n\n**Sanity range.** A price fans out to every connected portal, so a typo is stopped here. `price_amount` must be between 100.000 and 2.000.000.000 CLP (a UF price is bounded at the day's UF; other currencies are not bounded) and `odometer_value` between 0 and 2.000.000; `null` clears either and is always accepted. Outside the range: **422 `VEHICLE_VALUE_OUT_OF_RANGE`**, with a Spanish `message`, `field_errors.<field>` and `details.violations[]` (`field`, `reason` = `price_out_of_range` | `km_out_of_range`, `value`, `min`, `max`, `currency`). A price change beyond 10x up or down from the current price answers **422 `PRICE_CHANGE_CONFIRMATION_REQUIRED`** (`details.reason` = `large_price_change`, `current_price`, `requested_price`, `currency`, `factor`, `confirm_flag`) unless the body carries `confirm_large_price_change: true`; ask the user, then resend the same body with it. Nothing is written on either 422.\n\n**Normalization and `catalog_match`.** On create and update, `make` and `model` that equal an entry of GET /vehicles/catalog/brands and /catalog/models up to case, accents, spaces and hyphens are stored in the catalog's spelling (\"toyota\" → \"Toyota\", \"mercedes benz\" → \"Mercedes-Benz\", \"rav 4\" → \"RAV4\"); `fuel_type`, `gear_type` and `body_style` are stored as vocabulary codes (\"Bencina\" → `gasolina`, \"Automática\" → `automatica`, \"Station Wagon\" → `wagon`; codes: fuel `gasolina|diesel|hibrido|electrico|gnc|glp|otro`, gear `mecanica|automatica|otro`, body `sedan|hatchback|suv|camioneta|coupe|wagon|furgon|convertible|minivan|otro`). Nothing is invented: a value with no match is stored exactly as sent. The response (and GET /vehicles/{id}) carries `catalog_match`: `make` / `model` are `matched` | `unmatched` | `missing` (`not_applicable` for a non-`auto` vehicle_type); `version` adds `approximate` (word overlap with a catalog version, same displacement) and `catalog_version` names the catalog version it matched or most resembles; `fuel_type` / `gear_type` / `body_style` are `matched` | `unmatched` | `missing`; `warnings[]` lists `{ field, code, message }` for everything not matched, with a Spanish `message` ready to show (codes: `make_missing`, `make_not_in_catalog`, `model_missing`, `model_not_in_catalog`, `version_approximate`, `version_not_in_catalog`, `fuel_type_not_recognized`, `gear_type_not_recognized`, `body_style_not_recognized`). A make or model that is not matched still saves, but Chileautos publishes it without a technical sheet (ficha). `catalog_match` is null only when the catalog could not be loaded. Example of an unmatched push: {\"make\":\"matched\",\"model\":\"unmatched\",\"version\":\"unmatched\",\"catalog_version\":null,\"fuel_type\":\"unmatched\",\"gear_type\":\"matched\",\"body_style\":\"missing\",\"warnings\":[{\"field\":\"model\",\"code\":\"model_not_in_catalog\",\"message\":\"Modelo 'Rav-4 xx' no está en el catálogo de Toyota (GET /vehicles/catalog/models); Chileautos lo publicará sin ficha.\"},{\"field\":\"fuel_type\",\"code\":\"fuel_type_not_recognized\",\"message\":\"Combustible 'Hidrógeno' no se reconoce; valores aceptados: gasolina, diesel, hibrido, electrico, gnc, glp, otro.\"}]}\n\n**Photos are re-hosted.** A `photos[].url` that is not already a Vitrina media url is copied, shortly AFTER the response, onto the Vitrina media host and the stored url is rewritten to that copy, in the same order with every other photo field kept. The copy is normalized: EXIF orientation applied to the pixels, EXIF/GPS/XMP removed, long edge capped at 2560 px, re-encoded only when it had to change. Only public `https` urls are fetched (private/internal addresses are refused) and only image bodies up to 12 MB; a photo that cannot be fetched keeps the url you sent. Re-sending a url that was already copied is stored as the copy straight away, so pushing the same gallery again is not a change. The response of THIS call still shows the urls as sent; read the vehicle again to see the rewritten ones. The rewrite advances `updated_at` but writes no history entry, triggers no portal republish and emits no webhook.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "make": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "short_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "price_currency": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "odometer_value": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "odometer_unit": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "fuel_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "gear_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "body_style": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "doors": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10
                  },
                  "registration_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "vin": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 32
                  },
                  "listing_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "record_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "merch_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "oferta",
                      "liquidacion",
                      "poco_km",
                      "cuatro_x_cuatro",
                      "full_equipo",
                      "recien_llegado",
                      "unico_dueno"
                    ]
                  },
                  "is_featured": {
                    "type": "boolean"
                  },
                  "sold_outside_vitrina": {
                    "type": "boolean"
                  },
                  "vehicle_type": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "camion",
                      "maquinaria",
                      "nautico"
                    ]
                  },
                  "sale_status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "disponible",
                      "reservado",
                      "vendido"
                    ]
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "estacionamiento": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "watch_enabled": {
                    "type": "boolean"
                  },
                  "floor_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "target_position": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 25,
                    "maximum": 75
                  },
                  "abono_amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "order": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "focal": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "x": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "y": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "manual",
                                "auto"
                              ],
                              "default": "manual"
                            }
                          },
                          "required": [
                            "x",
                            "y"
                          ]
                        }
                      },
                      "required": [
                        "url"
                      ]
                    }
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "keys_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "municipalidad_comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  },
                  "last_service_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "service_odometer_km": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "responsible_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "publish_to": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "OPTIONAL. Marketplaces to publish this vehicle to right after it is saved — the same effect as a follow-up POST /vehicles/{id}/publish, in one call. Omitted or `[]` means save only and publish nowhere: there is no default and no tenant-level setting. Each entry is either a portal slug (`chileautos`, `chileautos_panel`, `linze`, `mercadolibre`, `veekls`, `yapo`, `yapo_panel`) or a marketplace integration id (the `id` of a row from GET /marketplaces). A portal slug resolves to the connected account the workspace has for that portal, whichever transport it uses (`chileautos` reaches the certified API account or the `chileautos_panel` account). Every entry is validated BEFORE the vehicle is written, and any bad entry answers 422 with nothing saved: a slug with no connected account, a slug matching more than one account (pass the integration id instead), an id that is not one of this workspace's accounts, or a portal that does not accept this vehicle type. Entries resolving to the same account are deduplicated. A portal FAILING to publish never fails the save — see `publications` in the response. On PUT, `publish_to` only ADDS portals: a portal where the vehicle is already live or queued is a no-op (`already_published`), never a second ad, and a portal left out is NOT unpublished — removal is DELETE /vehicles/{id}/publications/{pubId}.",
                    "example": [
                      "chileautos",
                      "mercadolibre"
                    ]
                  }
                }
              },
              "example": {
                "make": "Toyota",
                "model": "Corolla",
                "version": "1.8 XEI CVT",
                "year": 2022,
                "price_amount": 13990000,
                "odometer_value": 42500,
                "fuel_type": "Bencina",
                "gear_type": "Automática",
                "body_style": "Sedán",
                "color": "Gris",
                "registration_number": "FGXY21",
                "vin": "9BWZZZ377NP004512",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                "publish_to": [
                  "chileautos",
                  "mercadolibre"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Vehicle. Carries `publications` only when `publish_to` was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "make": "Toyota",
                    "model": "Corolla",
                    "version": "1.8 XEI CVT",
                    "year": 2022,
                    "title": null,
                    "short_title": null,
                    "price_amount": 13990000,
                    "price_clp": 13990000,
                    "watch_enabled": false,
                    "floor_price_clp": null,
                    "target_position": null,
                    "abono_amount_clp": 300000,
                    "merged_into": null,
                    "deleted_at": null,
                    "price_currency": "CLP",
                    "odometer_value": 42500,
                    "odometer_unit": "KM",
                    "fuel_type": "gasolina",
                    "gear_type": "automatica",
                    "body_style": "sedan",
                    "color": "Gris",
                    "doors": 4,
                    "registration_number": "FGXY21",
                    "vin": "9BWZZZ377NP004512",
                    "plate_normalized": "FGXY21",
                    "listing_type": "Usado",
                    "record_type": null,
                    "type": "Car",
                    "merch_label": null,
                    "featured_at": null,
                    "vehicle_type": "auto",
                    "sale_status": null,
                    "status": "disponible",
                    "reserved_at": null,
                    "reserved_by_name": null,
                    "sold_at": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "estacionamiento": null,
                    "description": "Mantenimientos al día en concesionario.",
                    "photos": [
                      {
                        "url": "https://images.example.cl/corolla-2022-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {},
                    "tags": [],
                    "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                    "active": true,
                    "protected_fields": [],
                    "deactivated_by": null,
                    "tenencia": "propio",
                    "tenencia_source": "default",
                    "tenencia_declared_by": null,
                    "tenencia_declared_at": null,
                    "iva_regime": null,
                    "keys_count": null,
                    "municipalidad_comuna_code": null,
                    "last_service_date": null,
                    "service_odometer_km": null,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-09-15T18:34:48.575Z",
                    "updated_at": "2026-09-15T18:34:48.575Z",
                    "live_sale_note": null,
                    "active_reservation": null,
                    "sin_costo": true,
                    "not_on_lot": false,
                    "catalog_match": {
                      "make": "matched",
                      "model": "matched",
                      "version": "approximate",
                      "catalog_version": "1.8 XEI CVT",
                      "fuel_type": "matched",
                      "gear_type": "matched",
                      "body_style": "matched",
                      "warnings": [
                        {
                          "field": "version",
                          "code": "version_approximate",
                          "message": "Versión '1.8 XEI CVT full' no coincide exactamente con el catálogo; la más parecida es '1.8 XEI CVT'."
                        }
                      ]
                    },
                    "publications": [
                      {
                        "integration_id": "33333333-0000-4000-8000-000000000003",
                        "provider": "chileautos_panel",
                        "status": "pending",
                        "external_id": null,
                        "publication": {
                          "id": "88888888-0000-4000-8000-000000000003",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                          "marketplace_account_id": "33333333-0000-4000-8000-000000000003",
                          "provider": "chileautos_panel",
                          "external_id": null,
                          "status": "pending",
                          "origin": "vitrina",
                          "permalink": null,
                          "last_synced_at": null,
                          "last_published_at": null,
                          "last_error": null,
                          "close_retry_count": 0,
                          "portal_state": null,
                          "portal_state_at": null,
                          "last_push_status": "updated",
                          "last_push_at": "2026-09-19T09:58:00.000Z",
                          "last_push_error": null,
                          "last_push_fields": [
                            "priceAmount"
                          ],
                          "push_delivery": "api",
                          "raw_provider_payload": null,
                          "created_at": "2026-09-16T09:00:00.000Z",
                          "updated_at": "2026-09-19T10:00:00.000Z"
                        }
                      },
                      {
                        "integration_id": "33333333-0000-4000-8000-000000000001",
                        "provider": "mercadolibre",
                        "status": "published",
                        "external_id": "MLC-1234567890",
                        "publication": {
                          "id": "88888888-0000-4000-8000-000000000001",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                          "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                          "provider": "mercadolibre",
                          "external_id": "MLC-1234567890",
                          "status": "published",
                          "origin": "vitrina",
                          "permalink": "https://articulo.mercadolibre.cl/MLC-1234567890",
                          "last_synced_at": "2026-09-19T10:00:00.000Z",
                          "last_published_at": "2026-09-16T09:00:00.000Z",
                          "last_error": null,
                          "close_retry_count": 0,
                          "portal_state": "active",
                          "portal_state_at": "2026-09-19T10:00:00.000Z",
                          "last_push_status": "updated",
                          "last_push_at": "2026-09-19T09:58:00.000Z",
                          "last_push_error": null,
                          "last_push_fields": [
                            "priceAmount"
                          ],
                          "push_delivery": "api",
                          "raw_provider_payload": null,
                          "created_at": "2026-09-16T09:00:00.000Z",
                          "updated_at": "2026-09-19T10:00:00.000Z"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`VEHICLE_VALUE_OUT_OF_RANGE` (price or km outside the sanity range) or `PRICE_CHANGE_CONFIRMATION_REQUIRED` (a price change beyond 10x sent without `confirm_large_price_change: true`). Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/export": {
      "get": {
        "summary": "Export the Stock list as a PDF hoja de patio or an Excel table",
        "description": "Exports exactly what the Stock screen shows, under the SAME filters and the same `marketplace:read` scope as GET /vehicles — no new access path. `format` picks the artifact: `pdf` is the printed walking sheet (grouped by Sucursal, ordered by sucursal + estacionamiento so it follows the patio on foot), `xlsx` is the full table. The two differ in ONE default: `include_inactive` defaults to true for pdf (an inactivo is not published but is still parked in the patio, so a walking sheet must list it) and false for xlsx. Passing `ids` scopes the export to that selection and makes `include_inactive` moot — a selected vehicle is always exported. Internal prices (piso / mínimo del dueño / target) are opt-in via `include_internal_prices` and NEVER appear on the pdf. ⚠ SINCE ERP S3-6 THE OPT-IN IS NECESSARY BUT NOT SUFFICIENT: piso and mínimo del dueño are per-unit money figures that disclose the dealership cost position, so the whole internal-price block is withheld unless the recipient of the file also holds `dealership_economics:read`. The columns are ABSENT from the sheet, not blank. Above 2000 matching rows (STOCK_EXPORT_ROW_CAP in stock-export.service.ts — not imported here, since this file deliberately depends on schemas only and that service pulls in the pdf/xlsx renderers) the export is refused with 413 rather than truncated silently. `tenencia` / `consignacion_modalidad` (Tenencia, ADR 0025) narrow the sheet exactly as they narrow the screen, and carry the same 400 when the modalidad arrives without `tenencia=consignacion`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "pdf"
              ],
              "default": "xlsx"
            },
            "required": false,
            "name": "format",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "published",
                "unpublished",
                "error",
                "stale",
                "source_deactivated",
                "sin_costo",
                "mandato_sin_firmar"
              ]
            },
            "required": false,
            "name": "view",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "disponible",
                "reservado",
                "vendido"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "camion",
                "maquinaria",
                "nautico"
              ]
            },
            "required": false,
            "name": "vehicle_type",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "has_error",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "needs_review",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "sin_estacionamiento",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "sin_sucursal",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "papers_expired",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "papers_expiring",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "propio",
                "consignacion"
              ]
            },
            "required": false,
            "name": "tenencia",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "en_local",
                "virtual",
                "sin_contrato"
              ]
            },
            "required": false,
            "name": "consignacion_modalidad",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "document",
                "declared",
                "default",
                "decided"
              ]
            },
            "required": false,
            "name": "tenencia_source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "price_asc",
                "price_desc",
                "year_desc",
                "year_asc",
                "km_asc",
                "km_desc",
                "recent",
                "published_asc",
                "published_desc",
                "created_asc",
                "created_desc",
                "days_desc",
                "interest_desc",
                "estacionamiento"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "ids",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "include_inactive",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "include_internal_prices",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The export file. `Content-Disposition` carries the generated filename (sucursal + date); it is exposed via CORS so a browser fetch can actually read it.",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"stock-<sucursal|todas>-<YYYY-MM-DD>.<ext>\"",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Too many matching rows. The error details carry `row_count` and `limit` so the caller can tell the user how much to narrow by.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}": {
      "get": {
        "summary": "Get a vehicle",
        "description": "FIELD-LEVEL COST OMISSION (ERP S3-6): the per-unit money position — `floor_price_clp` (precio piso) — is withheld unless the caller holds `dealership_economics:read`. The key is ABSENT from the response object, not null: a caller who checks `'floor_price_clp' in body` sees it was never offered. `price_clp` (the asking price) is unaffected — it is the one money figure about a unit that is meant to be seen. The scope is NOT an alternative to `marketplace:read` (requireScope ORs its varargs, which would widen this route rather than narrow the field); it is an addition.\n\nTENENCIA (ADR 0025): the response carries `tenencia` and `not_on_lot` — the SAME two signals the Stock list row carries (composeStockRows), resolved the SAME way (`consignmentRepo.listActiveModalidadByVehicleIds` + `isOffLot`), so the ficha can never disagree with the list about which cars are off the lot. `not_on_lot` is true iff an ACTIVE contract with `modalidad=virtual` exists; `tenencia` alone cannot answer it, because the modalidad lives on the contract.\n\n**Normalization and `catalog_match`.** On create and update, `make` and `model` that equal an entry of GET /vehicles/catalog/brands and /catalog/models up to case, accents, spaces and hyphens are stored in the catalog's spelling (\"toyota\" → \"Toyota\", \"mercedes benz\" → \"Mercedes-Benz\", \"rav 4\" → \"RAV4\"); `fuel_type`, `gear_type` and `body_style` are stored as vocabulary codes (\"Bencina\" → `gasolina`, \"Automática\" → `automatica`, \"Station Wagon\" → `wagon`; codes: fuel `gasolina|diesel|hibrido|electrico|gnc|glp|otro`, gear `mecanica|automatica|otro`, body `sedan|hatchback|suv|camioneta|coupe|wagon|furgon|convertible|minivan|otro`). Nothing is invented: a value with no match is stored exactly as sent. The response (and GET /vehicles/{id}) carries `catalog_match`: `make` / `model` are `matched` | `unmatched` | `missing` (`not_applicable` for a non-`auto` vehicle_type); `version` adds `approximate` (word overlap with a catalog version, same displacement) and `catalog_version` names the catalog version it matched or most resembles; `fuel_type` / `gear_type` / `body_style` are `matched` | `unmatched` | `missing`; `warnings[]` lists `{ field, code, message }` for everything not matched, with a Spanish `message` ready to show (codes: `make_missing`, `make_not_in_catalog`, `model_missing`, `model_not_in_catalog`, `version_approximate`, `version_not_in_catalog`, `fuel_type_not_recognized`, `gear_type_not_recognized`, `body_style_not_recognized`). A make or model that is not matched still saves, but Chileautos publishes it without a technical sheet (ficha). `catalog_match` is null only when the catalog could not be loaded. Example of an unmatched push: {\"make\":\"matched\",\"model\":\"unmatched\",\"version\":\"unmatched\",\"catalog_version\":null,\"fuel_type\":\"unmatched\",\"gear_type\":\"matched\",\"body_style\":\"missing\",\"warnings\":[{\"field\":\"model\",\"code\":\"model_not_in_catalog\",\"message\":\"Modelo 'Rav-4 xx' no está en el catálogo de Toyota (GET /vehicles/catalog/models); Chileautos lo publicará sin ficha.\"},{\"field\":\"fuel_type\",\"code\":\"fuel_type_not_recognized\",\"message\":\"Combustible 'Hidrógeno' no se reconoce; valores aceptados: gasolina, diesel, hibrido, electrico, gnc, glp, otro.\"}]}",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "make": "Toyota",
                    "model": "Corolla",
                    "version": "1.8 XEI CVT",
                    "year": 2022,
                    "title": null,
                    "short_title": null,
                    "price_amount": 13990000,
                    "price_clp": 13990000,
                    "watch_enabled": false,
                    "floor_price_clp": null,
                    "target_position": null,
                    "abono_amount_clp": 300000,
                    "merged_into": null,
                    "deleted_at": null,
                    "price_currency": "CLP",
                    "odometer_value": 42500,
                    "odometer_unit": "KM",
                    "fuel_type": "gasolina",
                    "gear_type": "automatica",
                    "body_style": "sedan",
                    "color": "Gris",
                    "doors": 4,
                    "registration_number": "FGXY21",
                    "vin": "9BWZZZ377NP004512",
                    "plate_normalized": "FGXY21",
                    "listing_type": "Usado",
                    "record_type": null,
                    "type": "Car",
                    "merch_label": null,
                    "featured_at": null,
                    "vehicle_type": "auto",
                    "sale_status": null,
                    "status": "disponible",
                    "reserved_at": null,
                    "reserved_by_name": null,
                    "sold_at": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "estacionamiento": null,
                    "description": "Mantenimientos al día en concesionario.",
                    "photos": [
                      {
                        "url": "https://images.example.cl/corolla-2022-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {},
                    "tags": [],
                    "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                    "active": true,
                    "protected_fields": [],
                    "deactivated_by": null,
                    "tenencia": "propio",
                    "tenencia_source": "default",
                    "tenencia_declared_by": null,
                    "tenencia_declared_at": null,
                    "iva_regime": null,
                    "keys_count": null,
                    "municipalidad_comuna_code": null,
                    "last_service_date": null,
                    "service_odometer_km": null,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-09-15T18:34:48.575Z",
                    "updated_at": "2026-09-15T18:34:48.575Z",
                    "live_sale_note": null,
                    "active_reservation": null,
                    "sin_costo": true,
                    "not_on_lot": false,
                    "catalog_match": {
                      "make": "matched",
                      "model": "matched",
                      "version": "approximate",
                      "catalog_version": "1.8 XEI CVT",
                      "fuel_type": "matched",
                      "gear_type": "matched",
                      "body_style": "matched",
                      "warnings": [
                        {
                          "field": "version",
                          "code": "version_approximate",
                          "message": "Versión '1.8 XEI CVT full' no coincide exactamente con el catálogo; la más parecida es '1.8 XEI CVT'."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a vehicle",
        "description": "Updating registration_number re-derives plate_normalized; a collision with another ACTIVE vehicle of the tenant returns 409 (conflicto de patente). A CHANGED plate is held to the same Chilean formats as on POST /vehicles (400 `plate_format_invalid`); re-sending the stored value unchanged is never refused.\n\n**`publish_to` only ADDS portals.** Same contract as on POST /vehicles (validated before the edit is written; 422 writes nothing; a portal failure is an `error` entry in `publications`, never a failed request). A portal where the vehicle is already live or has a queued publish is a no-op (`already_published`) — never a second ad, so an ERP may safely retry. A portal LEFT OUT of `publish_to` is NOT unpublished: removal is DELETE /vehicles/{id}/publications/{pubId}. A body with only `publish_to` publishes without editing the vehicle.\n\n**Sanity range.** A price fans out to every connected portal, so a typo is stopped here. `price_amount` must be between 100.000 and 2.000.000.000 CLP (a UF price is bounded at the day's UF; other currencies are not bounded) and `odometer_value` between 0 and 2.000.000; `null` clears either and is always accepted. Outside the range: **422 `VEHICLE_VALUE_OUT_OF_RANGE`**, with a Spanish `message`, `field_errors.<field>` and `details.violations[]` (`field`, `reason` = `price_out_of_range` | `km_out_of_range`, `value`, `min`, `max`, `currency`). A price change beyond 10x up or down from the current price answers **422 `PRICE_CHANGE_CONFIRMATION_REQUIRED`** (`details.reason` = `large_price_change`, `current_price`, `requested_price`, `currency`, `factor`, `confirm_flag`) unless the body carries `confirm_large_price_change: true`; ask the user, then resend the same body with it. Nothing is written on either 422.\n\n**Normalization and `catalog_match`.** On create and update, `make` and `model` that equal an entry of GET /vehicles/catalog/brands and /catalog/models up to case, accents, spaces and hyphens are stored in the catalog's spelling (\"toyota\" → \"Toyota\", \"mercedes benz\" → \"Mercedes-Benz\", \"rav 4\" → \"RAV4\"); `fuel_type`, `gear_type` and `body_style` are stored as vocabulary codes (\"Bencina\" → `gasolina`, \"Automática\" → `automatica`, \"Station Wagon\" → `wagon`; codes: fuel `gasolina|diesel|hibrido|electrico|gnc|glp|otro`, gear `mecanica|automatica|otro`, body `sedan|hatchback|suv|camioneta|coupe|wagon|furgon|convertible|minivan|otro`). Nothing is invented: a value with no match is stored exactly as sent. The response (and GET /vehicles/{id}) carries `catalog_match`: `make` / `model` are `matched` | `unmatched` | `missing` (`not_applicable` for a non-`auto` vehicle_type); `version` adds `approximate` (word overlap with a catalog version, same displacement) and `catalog_version` names the catalog version it matched or most resembles; `fuel_type` / `gear_type` / `body_style` are `matched` | `unmatched` | `missing`; `warnings[]` lists `{ field, code, message }` for everything not matched, with a Spanish `message` ready to show (codes: `make_missing`, `make_not_in_catalog`, `model_missing`, `model_not_in_catalog`, `version_approximate`, `version_not_in_catalog`, `fuel_type_not_recognized`, `gear_type_not_recognized`, `body_style_not_recognized`). A make or model that is not matched still saves, but Chileautos publishes it without a technical sheet (ficha). `catalog_match` is null only when the catalog could not be loaded. Example of an unmatched push: {\"make\":\"matched\",\"model\":\"unmatched\",\"version\":\"unmatched\",\"catalog_version\":null,\"fuel_type\":\"unmatched\",\"gear_type\":\"matched\",\"body_style\":\"missing\",\"warnings\":[{\"field\":\"model\",\"code\":\"model_not_in_catalog\",\"message\":\"Modelo 'Rav-4 xx' no está en el catálogo de Toyota (GET /vehicles/catalog/models); Chileautos lo publicará sin ficha.\"},{\"field\":\"fuel_type\",\"code\":\"fuel_type_not_recognized\",\"message\":\"Combustible 'Hidrógeno' no se reconoce; valores aceptados: gasolina, diesel, hibrido, electrico, gnc, glp, otro.\"}]}\n\n**Photos are re-hosted.** A `photos[].url` that is not already a Vitrina media url is copied, shortly AFTER the response, onto the Vitrina media host and the stored url is rewritten to that copy, in the same order with every other photo field kept. The copy is normalized: EXIF orientation applied to the pixels, EXIF/GPS/XMP removed, long edge capped at 2560 px, re-encoded only when it had to change. Only public `https` urls are fetched (private/internal addresses are refused) and only image bodies up to 12 MB; a photo that cannot be fetched keeps the url you sent. Re-sending a url that was already copied is stored as the copy straight away, so pushing the same gallery again is not a change. The response of THIS call still shows the urls as sent; read the vehicle again to see the rewritten ones. The rewrite advances `updated_at` but writes no history entry, triggers no portal republish and emits no webhook.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "make": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "version": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "short_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "price_currency": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "odometer_value": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "odometer_unit": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "fuel_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "gear_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "body_style": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "doors": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10
                  },
                  "registration_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "vin": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 32
                  },
                  "listing_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "record_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "merch_label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "oferta",
                      "liquidacion",
                      "poco_km",
                      "cuatro_x_cuatro",
                      "full_equipo",
                      "recien_llegado",
                      "unico_dueno"
                    ]
                  },
                  "is_featured": {
                    "type": "boolean"
                  },
                  "sold_outside_vitrina": {
                    "type": "boolean"
                  },
                  "vehicle_type": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "camion",
                      "maquinaria",
                      "nautico"
                    ]
                  },
                  "sale_status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "disponible",
                      "reservado",
                      "vendido"
                    ]
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "estacionamiento": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "watch_enabled": {
                    "type": "boolean"
                  },
                  "floor_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "target_position": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 25,
                    "maximum": 75
                  },
                  "abono_amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "order": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "focal": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "x": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "y": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "manual",
                                "auto"
                              ],
                              "default": "manual"
                            }
                          },
                          "required": [
                            "x",
                            "y"
                          ]
                        }
                      },
                      "required": [
                        "url"
                      ]
                    }
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "keys_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "municipalidad_comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  },
                  "last_service_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "service_odometer_km": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "responsible_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "publish_to": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "OPTIONAL. Marketplaces to publish this vehicle to right after it is saved — the same effect as a follow-up POST /vehicles/{id}/publish, in one call. Omitted or `[]` means save only and publish nowhere: there is no default and no tenant-level setting. Each entry is either a portal slug (`chileautos`, `chileautos_panel`, `linze`, `mercadolibre`, `veekls`, `yapo`, `yapo_panel`) or a marketplace integration id (the `id` of a row from GET /marketplaces). A portal slug resolves to the connected account the workspace has for that portal, whichever transport it uses (`chileautos` reaches the certified API account or the `chileautos_panel` account). Every entry is validated BEFORE the vehicle is written, and any bad entry answers 422 with nothing saved: a slug with no connected account, a slug matching more than one account (pass the integration id instead), an id that is not one of this workspace's accounts, or a portal that does not accept this vehicle type. Entries resolving to the same account are deduplicated. A portal FAILING to publish never fails the save — see `publications` in the response. On PUT, `publish_to` only ADDS portals: a portal where the vehicle is already live or queued is a no-op (`already_published`), never a second ad, and a portal left out is NOT unpublished — removal is DELETE /vehicles/{id}/publications/{pubId}.",
                    "example": [
                      "chileautos",
                      "mercadolibre"
                    ]
                  },
                  "confirm_large_price_change": {
                    "type": "boolean",
                    "description": "Confirms a price change beyond 10x up or down from the current price. Without it such an edit answers 422 `PRICE_CHANGE_CONFIRMATION_REQUIRED`; resend the same body with `true` once the user confirmed. Not stored."
                  }
                }
              },
              "example": {
                "price_amount": 13490000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vehicle. Carries `publications` only when `publish_to` was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "make": "Toyota",
                    "model": "Corolla",
                    "version": "1.8 XEI CVT",
                    "year": 2022,
                    "title": null,
                    "short_title": null,
                    "price_amount": 13490000,
                    "price_clp": 13490000,
                    "watch_enabled": false,
                    "floor_price_clp": null,
                    "target_position": null,
                    "abono_amount_clp": 300000,
                    "merged_into": null,
                    "deleted_at": null,
                    "price_currency": "CLP",
                    "odometer_value": 42500,
                    "odometer_unit": "KM",
                    "fuel_type": "gasolina",
                    "gear_type": "automatica",
                    "body_style": "sedan",
                    "color": "Gris",
                    "doors": 4,
                    "registration_number": "FGXY21",
                    "vin": "9BWZZZ377NP004512",
                    "plate_normalized": "FGXY21",
                    "listing_type": "Usado",
                    "record_type": null,
                    "type": "Car",
                    "merch_label": null,
                    "featured_at": null,
                    "vehicle_type": "auto",
                    "sale_status": null,
                    "status": "disponible",
                    "reserved_at": null,
                    "reserved_by_name": null,
                    "sold_at": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "estacionamiento": null,
                    "description": "Mantenimientos al día en concesionario.",
                    "photos": [
                      {
                        "url": "https://images.example.cl/corolla-2022-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {},
                    "tags": [],
                    "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                    "active": true,
                    "protected_fields": [],
                    "deactivated_by": null,
                    "tenencia": "propio",
                    "tenencia_source": "default",
                    "tenencia_declared_by": null,
                    "tenencia_declared_at": null,
                    "iva_regime": null,
                    "keys_count": null,
                    "municipalidad_comuna_code": null,
                    "last_service_date": null,
                    "service_odometer_km": null,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-09-15T18:34:48.575Z",
                    "updated_at": "2026-09-15T18:34:48.575Z",
                    "live_sale_note": null,
                    "active_reservation": null,
                    "sin_costo": true,
                    "not_on_lot": false,
                    "catalog_match": {
                      "make": "matched",
                      "model": "matched",
                      "version": "approximate",
                      "catalog_version": "1.8 XEI CVT",
                      "fuel_type": "matched",
                      "gear_type": "matched",
                      "body_style": "matched",
                      "warnings": [
                        {
                          "field": "version",
                          "code": "version_approximate",
                          "message": "Versión '1.8 XEI CVT full' no coincide exactamente con el catálogo; la más parecida es '1.8 XEI CVT'."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`VEHICLE_VALUE_OUT_OF_RANGE` (price or km outside the sanity range) or `PRICE_CHANGE_CONFIRMATION_REQUIRED` (a price change beyond 10x sent without `confirm_large_price_change: true`). Nothing is written.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a vehicle",
        "description": "Soft-delete: the row and its history remain, `deleted_at` is stamped, and it drops out of every default list and out of the public stock API. Before the soft-delete it schedules the close of every live ad on every connected portal (the same retry-backed pipeline as `POST /vehicles/{id}/close-ads`). A vehicle a negocio holds (`reservado`, `vendido`, or carrying a live nota de venta) cannot be deleted: 409 with `details.code: \"vehicle_held_by_negocio\"`. Deleting an already deleted vehicle is a 204 no-op.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/vehicles/{id}/fields/release": {
      "post": {
        "summary": "Release Override local protection on vehicle fields",
        "description": "Drop protection (ADR 0023) on the named canonical fields so the next sync re-follows the source. Releasing an unprotected field is a no-op.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 60
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "fields"
                ]
              },
              "example": {
                "fields": [
                  "color"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "make": "Toyota",
                    "model": "Corolla",
                    "version": "1.8 XEI CVT",
                    "year": 2022,
                    "title": null,
                    "short_title": null,
                    "price_amount": 13990000,
                    "price_clp": 13990000,
                    "watch_enabled": false,
                    "floor_price_clp": null,
                    "target_position": null,
                    "abono_amount_clp": 300000,
                    "merged_into": null,
                    "deleted_at": null,
                    "price_currency": "CLP",
                    "odometer_value": 42500,
                    "odometer_unit": "KM",
                    "fuel_type": "Bencina",
                    "gear_type": "Automática",
                    "body_style": "Sedán",
                    "color": "Gris",
                    "doors": 4,
                    "registration_number": "FGXY21",
                    "vin": "9BWZZZ377NP004512",
                    "plate_normalized": "FGXY21",
                    "listing_type": "Usado",
                    "record_type": null,
                    "type": "Car",
                    "merch_label": null,
                    "featured_at": null,
                    "vehicle_type": "auto",
                    "sale_status": null,
                    "status": "disponible",
                    "reserved_at": null,
                    "reserved_by_name": null,
                    "sold_at": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "estacionamiento": null,
                    "description": "Mantenimientos al día en concesionario.",
                    "photos": [
                      {
                        "url": "https://images.example.cl/corolla-2022-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {},
                    "tags": [],
                    "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                    "active": true,
                    "protected_fields": [],
                    "deactivated_by": null,
                    "tenencia": "propio",
                    "tenencia_source": "default",
                    "tenencia_declared_by": null,
                    "tenencia_declared_at": null,
                    "iva_regime": null,
                    "keys_count": null,
                    "municipalidad_comuna_code": null,
                    "last_service_date": null,
                    "service_odometer_km": null,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-09-15T18:34:48.575Z",
                    "updated_at": "2026-09-15T18:34:48.575Z",
                    "live_sale_note": null,
                    "active_reservation": null,
                    "sin_costo": true,
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/fields/protect": {
      "post": {
        "summary": "Protect (lock) vehicle fields against sync",
        "description": "Explicitly pin the named canonical fields to their current value (ADR 0023, two-way candado) so no sync overwrites them — without editing the value first. Non-protectable names (price_amount, status, …) are a no-op. Symmetric to /fields/release.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 60
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "fields"
                ]
              },
              "example": {
                "fields": [
                  "price_amount"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "make": "Toyota",
                    "model": "Corolla",
                    "version": "1.8 XEI CVT",
                    "year": 2022,
                    "title": null,
                    "short_title": null,
                    "price_amount": 13990000,
                    "price_clp": 13990000,
                    "watch_enabled": false,
                    "floor_price_clp": null,
                    "target_position": null,
                    "abono_amount_clp": 300000,
                    "merged_into": null,
                    "deleted_at": null,
                    "price_currency": "CLP",
                    "odometer_value": 42500,
                    "odometer_unit": "KM",
                    "fuel_type": "Bencina",
                    "gear_type": "Automática",
                    "body_style": "Sedán",
                    "color": "Gris",
                    "doors": 4,
                    "registration_number": "FGXY21",
                    "vin": "9BWZZZ377NP004512",
                    "plate_normalized": "FGXY21",
                    "listing_type": "Usado",
                    "record_type": null,
                    "type": "Car",
                    "merch_label": null,
                    "featured_at": null,
                    "vehicle_type": "auto",
                    "sale_status": null,
                    "status": "disponible",
                    "reserved_at": null,
                    "reserved_by_name": null,
                    "sold_at": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "estacionamiento": null,
                    "description": "Mantenimientos al día en concesionario.",
                    "photos": [
                      {
                        "url": "https://images.example.cl/corolla-2022-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {},
                    "tags": [],
                    "searchable_text": "toyota corolla 1.8 xei cvt sedán bencina automática gris 2022",
                    "active": true,
                    "protected_fields": [],
                    "deactivated_by": null,
                    "tenencia": "propio",
                    "tenencia_source": "default",
                    "tenencia_declared_by": null,
                    "tenencia_declared_at": null,
                    "iva_regime": null,
                    "keys_count": null,
                    "municipalidad_comuna_code": null,
                    "last_service_date": null,
                    "service_odometer_km": null,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-09-15T18:34:48.575Z",
                    "updated_at": "2026-09-15T18:34:48.575Z",
                    "live_sale_note": null,
                    "active_reservation": null,
                    "sin_costo": true,
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/history": {
      "get": {
        "summary": "A vehicle's change history (Historial)",
        "description": "Reverse-chronological audit trail for this vehicle — created, field edits (with a before→after diff), publish/unpublish/cierre, and Override local protect/release — with teammate actors resolved to display names. It also carries the deal's milestones, read from each document's own audit rows: nota de compra emitted/voided (`purchase_note_issued`, `purchase_note_voided`), nota de venta emitted/approved/voided (`sale_note_issued`, `sale_note_approved`, `sale_note_voided`), contrato de compraventa first printed (`compraventa_printed`), mandato de consignación first printed (`mandato_issued`), a signed copy filed (`signed_copy_filed`, with `metadata.signed_document_type`) and the liquidación paid to the consignor (`liquidacion_paid`). `metadata.folio` names the document when it has one. Each document type appears only for a caller holding its read scope (`purchase_notes:read`, `sale_notes:read`, `consignments:read`; signed copies also need `vehicle_registry:read`). A download of the Resumen del negocio is `deal_settlement_downloaded`. The car's papers (ADR 0114 §7 — permiso de circulación, revisión técnica, SOAP, padrón, CAV, any named paper) appear as `paper_added` (a file filed; `metadata.replaced_attachment_id` when it replaced one), `paper_updated` (renamed or its «Vence» day set, `metadata.changed`) and `paper_removed`, each with `metadata.kind`, `metadata.label` and `metadata.expires_on` — only for a caller holding `vehicle_registry:read`. A value changed directly on a connected system (price, km; Veekls also year and version) and taken into Vitrina is `portal_edit` (system actor, `changes`, `metadata.provider`, `metadata.portal`, `metadata.summary`); a portal value that was not taken in is `portal_edit_ignored` (`metadata.summary`; `metadata.ignored`: field, portal_value, kept_value, reason = `pending_vitrina_edit` when it lost to a Vitrina edit still waiting to be pushed, `out_of_range` when it fell outside the price/km sanity range). The UI merges it with /activity (leads) into one \"Leads y cambios\" timeline.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Vehicle history entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "99999999-0000-4000-8000-000000000005",
                      "action": "sale_notes.approve",
                      "kind": "sale_note_approved",
                      "actor": {
                        "kind": "user",
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Ana Torres"
                      },
                      "changes": [],
                      "metadata": {
                        "folio": "V-13",
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001"
                      },
                      "at": "2026-09-18T15:02:11.000Z"
                    },
                    {
                      "id": "99999999-0000-4000-8000-000000000003",
                      "action": "vehicles.create",
                      "kind": "created",
                      "actor": {
                        "kind": "user",
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Ana Torres"
                      },
                      "changes": [],
                      "metadata": {},
                      "at": "2026-09-15T18:34:58.390Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/document-discrepancies": {
      "get": {
        "summary": "The workspace queue of document-vs-stock disagreements",
        "description": "Every disagreement between an official document and this workspace’s stock still waiting on a person, newest first. `standing` defaults to `open` — a queue is what is OUTSTANDING.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "resolved"
              ]
            },
            "required": false,
            "name": "standing",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Recorded document discrepancies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "66666666-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "attachment_kind": "padron",
                      "field": "registration_number",
                      "document_value": "FGXY22",
                      "observed_stock_value": "FGXY21",
                      "note": "El padrón trae una letra distinta a la patente registrada.",
                      "observed_at": "2026-09-18T14:00:00.000Z",
                      "observed_by": "11111111-0000-4000-8000-000000000001",
                      "resolution": null,
                      "resolution_reason": null,
                      "resolved_on": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "applied_prior_value": null,
                      "created_at": "2026-09-18T14:00:00.000Z",
                      "updated_at": "2026-09-18T14:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/document-discrepancies": {
      "get": {
        "summary": "What this car’s official papers disagree with",
        "description": "Each row carries what the document says, what stock said when it was read, and — once decided — who decided, why, and what stock held at that instant (`applied_prior_value`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "resolved"
              ]
            },
            "required": false,
            "name": "standing",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Recorded document discrepancies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "66666666-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "attachment_kind": "padron",
                      "field": "registration_number",
                      "document_value": "FGXY22",
                      "observed_stock_value": "FGXY21",
                      "note": "El padrón trae una letra distinta a la patente registrada.",
                      "observed_at": "2026-09-18T14:00:00.000Z",
                      "observed_by": "11111111-0000-4000-8000-000000000001",
                      "resolution": null,
                      "resolution_reason": null,
                      "resolved_on": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "applied_prior_value": null,
                      "created_at": "2026-09-18T14:00:00.000Z",
                      "updated_at": "2026-09-18T14:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record that an official document disagrees with stock",
        "description": "⚠ WRITES NOTHING BUT THE OBSERVATION — the vehicle is untouched, and stays untouched until a person applies it. `attachment_id` points into the ERP S15-20 vehicle expediente (never raw bytes), must belong to THIS car, and its kind must be one of `padron`, `factura`, `certificado_anotaciones` — all three issued OUTSIDE the dealership. A `contrato` is refused: a compraventa is dealer-asserted, and this is the one place an external fact may overrule a dealer-asserted one. `field` is bounded to the six a registry actually states — an unbounded list would let a factura \"correct\" the asking price. A reading that AGREES with stock is refused; it is not a discrepancy.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "attachment_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "field": {
                    "type": "string",
                    "enum": [
                      "registration_number",
                      "vin",
                      "year",
                      "make",
                      "model",
                      "color"
                    ]
                  },
                  "document_value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "attachment_id",
                  "field",
                  "document_value"
                ],
                "additionalProperties": false
              },
              "example": {
                "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                "field": "registration_number",
                "document_value": "FGXY22",
                "note": "El padrón trae una letra distinta a la patente registrada."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded discrepancy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "66666666-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "attachment_kind": "padron",
                    "field": "registration_number",
                    "document_value": "FGXY22",
                    "observed_stock_value": "FGXY21",
                    "note": "El padrón trae una letra distinta a la patente registrada.",
                    "observed_at": "2026-09-18T14:00:00.000Z",
                    "observed_by": "11111111-0000-4000-8000-000000000001",
                    "resolution": null,
                    "resolution_reason": null,
                    "resolved_on": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "applied_prior_value": null,
                    "created_at": "2026-09-18T14:00:00.000Z",
                    "updated_at": "2026-09-18T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/document-discrepancies/{discrepancyId}/apply": {
      "post": {
        "summary": "Apply the document — the document wins",
        "description": "Rewrites the field to what the document says, THROUGH the same ownership-aware write path a hand edit takes: the corrected field joins the vehicle’s Override local set (ADR 0023), so the next portal sync does not undo it. What stock held at that instant is preserved on the discrepancy and stamped into the car’s Historial. `reason` is mandatory; a second decision is a 409, and there is deliberately no un-apply verb — release the field to hand it back to sync.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "discrepancyId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El padrón es el vigente; se corrige la patente en stock."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The applied discrepancy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "66666666-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "attachment_kind": "padron",
                    "field": "registration_number",
                    "document_value": "FGXY22",
                    "observed_stock_value": "FGXY21",
                    "note": "El padrón trae una letra distinta a la patente registrada.",
                    "observed_at": "2026-09-18T14:00:00.000Z",
                    "observed_by": "11111111-0000-4000-8000-000000000001",
                    "resolution": "applied",
                    "resolution_reason": "El padrón es el vigente; se corrige la patente en stock.",
                    "resolved_on": "2026-09-19T09:00:00.000Z",
                    "resolved_at": "2026-09-19T09:00:00.000Z",
                    "resolved_by": "11111111-0000-4000-8000-000000000001",
                    "applied_prior_value": "FGXY21",
                    "created_at": "2026-09-18T14:00:00.000Z",
                    "updated_at": "2026-09-18T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/suggestions": {
      "get": {
        "summary": "Paperwork this hand-moved unit is still missing",
        "description": "PENDING suggestions only. A dealer who marks a car `vendido` or `reservado` directly — the quick status control on the Stock card — gets the state change immediately and unconditionally; what they also get is a nudge toward the document that normally moves it (`crear_nota_venta` / `crear_reserva`). ⚠ A SUGGESTION IS NEVER A PENDING WRITE: nothing here has touched the vehicle, the sale note or the reserva, and ignoring it forever is a supported outcome. The nudge is only raised when the document is GENUINELY ABSENT, so a `vendido` caused by approving a nota de venta — and a `reservado` caused by taking a reserva — produce nothing here. It disappears from this list when a person dismisses it or when the document it asked for is created; there is deliberately no create verb.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending workflow suggestions for this vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "77777777-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "crear_nota_venta",
                      "created_at": "2026-09-19T16:00:00.000Z",
                      "dismissed_at": null,
                      "dismissed_by": null,
                      "resolved_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/suggestions/{suggestionId}/dismiss": {
      "post": {
        "summary": "Dismiss the paperwork nudge",
        "description": "\"No, this one really is just a status change.\" Stamps the suggestion with the acting principal — a dismissal nobody signed is not a decision, it is a task that quietly stopped being displayed — and removes it from the pending list for good. Compare-and-swap: a second dismissal, or dismissing one the system already resolved because the document now exists, is a 404. The `{suggestionId}` must belong to `{id}`; one that belongs to another car of the same workspace is a 404 rather than a silent success. ⚠ NEVER TOUCHES THE VEHICLE: dismissing the nudge to write a nota de venta does not un-sell the car.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "suggestionId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dismissal acknowledged (`{ ok: true }`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "ok": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/document-discrepancies/{discrepancyId}/dismissal": {
      "post": {
        "summary": "Dismiss the document — stock stands",
        "description": "Records that the document does NOT overrule the stock record. ⚠ NEVER touches stock, and carries the same actor and the same MANDATORY `reason` as an apply: a dismissal nobody signed is not a decision, it is a discrepancy that quietly stopped being displayed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "discrepancyId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El padrón es el vigente; se corrige la patente en stock."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The dismissed discrepancy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "66666666-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "attachment_id": "f1f1f1f1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "attachment_kind": "padron",
                    "field": "registration_number",
                    "document_value": "FGXY22",
                    "observed_stock_value": "FGXY21",
                    "note": "El padrón trae una letra distinta a la patente registrada.",
                    "observed_at": "2026-09-18T14:00:00.000Z",
                    "observed_by": "11111111-0000-4000-8000-000000000001",
                    "resolution": "dismissed",
                    "resolution_reason": "El padrón es del dueño anterior y es previo al regrabado.",
                    "resolved_on": "2026-09-19T09:00:00.000Z",
                    "resolved_at": "2026-09-19T09:00:00.000Z",
                    "resolved_by": "11111111-0000-4000-8000-000000000001",
                    "applied_prior_value": null,
                    "created_at": "2026-09-18T14:00:00.000Z",
                    "updated_at": "2026-09-18T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/ficha-tecnica": {
      "get": {
        "summary": "The printable spec sheet for one unit",
        "description": "Renders the \"hoja de mesón\" — the counter sheet a customer walking the lot takes home — and streams it as `application/pdf`. ⚠ IT IS NOT AN ISSUANCE. Nothing is stored: no `rendered_document` row, no folio, no supersede chain, and no immutability claim of any kind. Calling it twice with the vehicle edited in between returns two DIFFERENT sheets, by design — the sheet is a render of the row as it is right now, which is why there is no companion metadata route and no stored-bytes hash to verify against (contrast `GET /consignments/{id}/contract/pdf`, which REFUSES to serve bytes whose sha256 disagrees with its row).\n\nEverything printed comes from the PUBLIC projection of the vehicle — the same DTO `GET /stock/{id}` serves to a dealer’s own website — plus the workspace’s branding and the unit’s branch. The plate, the VIN, the floor price, the target position, the IVA regime and the patio position are not expressible in that projection and never appear on the paper. Unlike `/stock/{id}`, there is NO status gate: an operator can print the sheet for a reservado or vendido unit they can already see. Scoped `marketplace:read` (a public-fields render, not dealer economics); 404 for another tenant’s vehicle, so the route is not an existence oracle.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The rendered sheet — US Letter, one or two pages. `Content-Disposition` carries a make/model filename slug.",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"ficha-tecnica-<make>-<model>.pdf\"",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/publications": {
      "get": {
        "summary": "List marketplace publications of a vehicle",
        "description": "Every publication row for this vehicle, one per connected portal it has ever been sent to — including a `deleted`/`error` row from a past close/unpublish, since a row here is a tombstone rather than an absence. `status` is the real `PublicationStatus` (never a boolean): `pending` (device-agent transport, facebook_marketplace — enqueued but not yet posted), `published`/`imported`/`updating`/`error` (live on the portal — `error` too, since only a later refresh failed), `removing` (device-agent transport — a removal was enqueued but not yet confirmed, so the ad is presumptively **still live**, the opposite fact from `pending`), `paused` (Cierre de avisos reversible hide, portals that support pausing) or `deleted` (gone, or a portal-specific terminal state like Chileautos’ `Withdrawn`). `external_id` is the portal’s own id for the ad; it is never a key inside Vitrina.\n\n**Per-portal push status.** After a dealer edit, each row reports the result of its last push: `last_push_status` is `updating` (the edit is queued, running or being retried), `updated` (the portal took it; `last_push_at` is when and `last_push_fields` lists what went out) or `failed` (the final retry failed: `last_push_error` is the portal’s reason in Spanish, `last_push_at` is when it gave up). A failed push retries automatically and the row stays `failed` until a later push succeeds; one portal failing never changes the others. `null` means never pushed. `push_delivery` says what `updated` means: `api` (the change is on the portal), `feed` (Yapo: the change is in the feed and shows when Yapo reads it) or `extension` (Facebook Marketplace: queued for the dealer’s browser extension). The same fields ride on `GET /vehicles/publications`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Publications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "88888888-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "external_id": "MLC-1234567890",
                      "status": "published",
                      "origin": "vitrina",
                      "permalink": "https://articulo.mercadolibre.cl/MLC-1234567890",
                      "last_synced_at": "2026-09-19T10:00:00.000Z",
                      "last_published_at": "2026-09-16T09:00:00.000Z",
                      "last_error": null,
                      "close_retry_count": 0,
                      "portal_state": "active",
                      "portal_state_at": "2026-09-19T10:00:00.000Z",
                      "last_push_status": "updated",
                      "last_push_at": "2026-09-19T09:58:00.000Z",
                      "last_push_error": null,
                      "last_push_fields": [
                        "priceAmount"
                      ],
                      "push_delivery": "api",
                      "raw_provider_payload": null,
                      "created_at": "2026-09-16T09:00:00.000Z",
                      "updated_at": "2026-09-19T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/publish": {
      "post": {
        "summary": "Publish a vehicle to one or more connected marketplaces",
        "description": "One round trip per portal named in `integration_ids`, and the response is a per-integration outcome list — a 200 does not mean every portal accepted the ad. `status` is `published` (Vitrina pushed or updated the ad), `already_published` (the double-publish guard found a live ad on this (vehicle, integration) that Vitrina did not itself push — nothing was sent, nothing was duplicated) or `error` (`error` names why).\n\n**Facebook Marketplace does not publish.** Vitrina only answers its buyers; naming a `facebook_marketplace` integration here answers `error`, and `facebook` is not a `publish_to` slug.\n\n**Chileautos (Panel) (`chileautos_panel`) is asynchronous.** It is driven through a browser session on the dealer’s control panel, which only Vitrina’s scraper workers can run, so this call answers `status: \"pending\"` the instant the job is queued (the publication row reads `pending`, or `updating` when the ad already exists). Same polling and webhooks as above; the row turns `published` or `error` (with `last_error`), usually within a few minutes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integration_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "integration_ids"
                ]
              },
              "example": {
                "integration_ids": [
                  "33333333-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Publish outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "integration_id": "33333333-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "status": "published",
                      "external_id": "MLC-1234567890",
                      "publication": {
                        "id": "88888888-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                        "provider": "mercadolibre",
                        "external_id": "MLC-1234567890",
                        "status": "published",
                        "origin": "vitrina",
                        "permalink": "https://articulo.mercadolibre.cl/MLC-1234567890",
                        "last_synced_at": "2026-09-19T10:00:00.000Z",
                        "last_published_at": "2026-09-16T09:00:00.000Z",
                        "last_error": null,
                        "close_retry_count": 0,
                        "portal_state": "active",
                        "portal_state_at": "2026-09-19T10:00:00.000Z",
                        "last_push_status": "updated",
                        "last_push_at": "2026-09-19T09:58:00.000Z",
                        "last_push_error": null,
                        "last_push_fields": [
                          "priceAmount"
                        ],
                        "push_delivery": "api",
                        "raw_provider_payload": null,
                        "created_at": "2026-09-16T09:00:00.000Z",
                        "updated_at": "2026-09-19T10:00:00.000Z"
                      }
                    },
                    {
                      "integration_id": "33333333-0000-4000-8000-000000000002",
                      "provider": "chileautos_panel",
                      "status": "pending",
                      "external_id": null,
                      "publication": {
                        "id": "88888888-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "marketplace_account_id": "33333333-0000-4000-8000-000000000002",
                        "provider": "chileautos_panel",
                        "external_id": null,
                        "status": "pending",
                        "origin": "vitrina",
                        "permalink": null,
                        "last_synced_at": "2026-09-19T10:00:00.000Z",
                        "last_published_at": "2026-09-16T09:00:00.000Z",
                        "last_error": null,
                        "close_retry_count": 0,
                        "portal_state": null,
                        "portal_state_at": null,
                        "last_push_status": "updated",
                        "last_push_at": "2026-09-19T09:58:00.000Z",
                        "last_push_error": null,
                        "last_push_fields": [
                          "priceAmount"
                        ],
                        "push_delivery": "api",
                        "raw_provider_payload": null,
                        "created_at": "2026-09-16T09:00:00.000Z",
                        "updated_at": "2026-09-19T10:00:00.000Z"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/publications/{pubId}": {
      "delete": {
        "summary": "Unpublish a vehicle from a marketplace",
        "description": "Removes ONE publication (contrast `POST /vehicles/{id}/close-ads`, which closes every portal at once and is always enqueued). Returns the publication row with `status`, the real `PublicationStatus` — whatever a portal calls its own removed state (Chileautos has no hard delete and writes `Withdrawn` on its side) still lands here as `deleted`. The row itself is kept as a tombstone, never removed from this table, so \"where did publication `pubId` go?\" always has an answer.\n\n⚠ **Facebook Marketplace has no HTTP API**, so this call enqueues a `device_task` for the extension exactly as publish does, and answers honestly: `status: \"removing\"`, never a fabricated `deleted`. The ad is presumptively **still live** on Facebook until the extension confirms the removal — poll `GET /vehicles/{id}/publications` for the row to turn `deleted` (or `error`, if the removal failed and the ad is still there), or subscribe to `vehicle.unpublished` / `vehicle.publish_failed`, fired only on that confirmation, never at enqueue. A `chileautos_panel` publication behaves the same way: the removal runs on Vitrina’s scraper workers, so the call answers `removing` and the row turns `deleted` (or `error`) when it completes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "pubId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Publication",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "88888888-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                    "provider": "mercadolibre",
                    "external_id": "MLC-1234567890",
                    "status": "deleted",
                    "origin": "vitrina",
                    "permalink": null,
                    "last_synced_at": "2026-09-19T10:00:00.000Z",
                    "last_published_at": "2026-09-16T09:00:00.000Z",
                    "last_error": null,
                    "close_retry_count": 0,
                    "portal_state": "active",
                    "portal_state_at": "2026-09-19T10:00:00.000Z",
                    "last_push_status": "updated",
                    "last_push_at": "2026-09-19T09:58:00.000Z",
                    "last_push_error": null,
                    "last_push_fields": [
                      "priceAmount"
                    ],
                    "push_delivery": "api",
                    "raw_provider_payload": null,
                    "created_at": "2026-09-16T09:00:00.000Z",
                    "updated_at": "2026-09-19T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/vehicles/close-ads-bulk": {
      "post": {
        "summary": "Close N vehicles' ads on every connected marketplace",
        "description": "Bulk cierre de avisos (ADR 0024) — the Stock bulk-bar \"despublicar\". ASYNC: enqueues one retry-backed cierre job per eligible vehicle and returns { enqueued, skipped } (skip reasons: not_found, no_publications), never the portals’ answers. intent defaults to \"deactivated\" (reversible hide: pause where the portal supports it, remove where it doesn’t); \"sold\" is the terminal close. Does NOT mark the vehicles Sold.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "sold",
                      "deactivated"
                    ],
                    "default": "deactivated"
                  }
                },
                "required": [
                  "vehicle_ids"
                ]
              },
              "example": {
                "vehicle_ids": [
                  "e1e1e1e1-0000-4000-8000-000000000001"
                ],
                "intent": "deactivated"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk cierre outcome",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enqueued": [
                      "e1e1e1e1-0000-4000-8000-000000000001"
                    ],
                    "skipped": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/estacionamientos": {
      "patch": {
        "summary": "Bulk-set vehicles' Estacionamiento (patio position)",
        "description": "The Sucursal tab-through save (ADR 0060 bulk path). ALL-OR-NOTHING: every vehicle_id is validated against this tenant BEFORE anything is written, and the whole batch lands in ONE transaction — unlike publish-bulk/close-ads-bulk above, there is no per-row partial success. A blank/empty estacionamiento clears the position (stores null). Never checks for duplicate positions across rows — sharing a spot is a legal double-park.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "vehicle_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "estacionamiento": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "vehicle_id",
                        "estacionamiento"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "assignments"
                ]
              },
              "example": {
                "assignments": [
                  {
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "estacionamiento": "A-12"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Count of vehicles updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "updated": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/tenencia": {
      "post": {
        "summary": "Declare a car propio — no paperwork required",
        "description": "Phase 2 (tenencia_source): the half of the ownership model consignación already had a two-field path for (POST /consignments/bulk → contrato mínimo). `tenencia` accepts ONLY `\"propio\"` — consignación keeps going through its own contrato mínimo. Per-vehicle verdicts in request order, NEVER all-or-nothing: `declared` (now tenencia_source=declared), `skipped_active_contract` (an ACTIVE consignment contract stands — end it first via POST /consignments/{id}/return, a devolución, rather than flipping it here), or `error` (with the reason). Up to 200 vehicles per call, de-duplicated, sequential. Scoped on `consignments:write` — \"the tenencia lifecycle\" — not `marketplace:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "tenencia": {
                    "type": "string",
                    "enum": [
                      "propio"
                    ]
                  }
                },
                "required": [
                  "vehicle_ids",
                  "tenencia"
                ]
              },
              "example": {
                "vehicle_ids": [
                  "e1e1e1e1-0000-4000-8000-000000000001"
                ],
                "tenencia": "propio"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-vehicle declaration outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "outcome": "declared"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/stock": {
      "get": {
        "summary": "List the public stock (dealer-website read API)",
        "description": "The tenant’s public lot: active vehicles that are not sold (vendido) nor hidden. `reservado` units are included with their reserved_at so the website can badge them. Filters cover brand/model/gearbox/fuel_type/body/type/status/featured, year/price/odometer/created ranges and sucursal; sort by price, published_at, created_at or featured_at (default created_desc). limit ≤ 100 (default 20) + offset. The plate is never exposed. Each item carries `merch_label` + `merch_label_es` — the dealer’s optional merchandising ribbon (oferta | liquidacion | poco_km | cuatro_x_cuatro | full_equipo | recien_llegado | unico_dueno), null when there is none. A tenant that turns `stock_publico.mostrar_vendidos` on ALSO gets the units sold in the last 90 days, flagged `status: \"vendido\"` with their `sold_at`, in the list AND in /stock/count; the switch is off by default and there is no caller-side override. Each item also carries `is_featured` + `featured_at` — whether the dealer PINNED this unit to the destacados grid of their portada, and when. `featured=true` narrows the selection to those pinned units (pair it with `sort=featured_desc`, their pinning order, newest pin first); that selection NEVER includes sold units, whatever `mostrar_vendidos` says, so a featured car that sells leaves the portada on its own. A tenant that turns `stock_publico.visit_coordination_disclosure` on (ADR 0095, off by default) additionally gets `visit_coordination_required: true` on a unit with an ACTIVE, modalidad=virtual consignment contract — a purely logistics fact (\"coordinate the visit with the owner\"), never the commercial one (`tenencia`/`modalidad` are never exposed, on this or any other public surface). The key is ABSENT — never `false` — on every other unit, opted-in or not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "brand",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "gearbox",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "fuel_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "body",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "Car",
                "Truck",
                "Motorcycle",
                "Boat"
              ]
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2100
            },
            "required": false,
            "name": "min_year",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2100
            },
            "required": false,
            "name": "max_year",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "min_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "max_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "min_odometer",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "max_odometer",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "sucursal",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "disponible",
                "reservado",
                "vendido"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "featured",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "price_asc",
                "price_desc",
                "published_asc",
                "published_desc",
                "created_asc",
                "created_desc",
                "featured_desc"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Public stock vehicles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "make": "Suzuki",
                      "model": "Swift",
                      "version": "GL 1.2",
                      "year": 2021,
                      "title": null,
                      "price": {
                        "amount": 9490000,
                        "currency": "CLP"
                      },
                      "odometer": {
                        "value": 31400,
                        "unit": "KM"
                      },
                      "fuel_type": null,
                      "fuel_label": null,
                      "gear_type": null,
                      "gear_label": null,
                      "body_style": null,
                      "body_label": null,
                      "color": null,
                      "doors": null,
                      "displacement_cc": null,
                      "type": "Car",
                      "type_label": "Auto",
                      "merch_label": null,
                      "merch_label_es": null,
                      "is_featured": false,
                      "featured_at": null,
                      "listing_type": "Usado",
                      "status": "disponible",
                      "reserved_at": null,
                      "sold_at": null,
                      "sucursal": {
                        "id": "b1b1b1b1-0000-4000-8000-000000000001",
                        "name": "Sucursal Providencia",
                        "address": null,
                        "address_street": "Av. Nueva Providencia",
                        "address_number": "2214",
                        "address_unit": null,
                        "comuna_code": "13123",
                        "comuna_name": "Providencia",
                        "region_code": "13"
                      },
                      "photos": [],
                      "description": null,
                      "tags": [],
                      "equipment": [],
                      "created_at": "2026-09-20T00:30:01.896Z",
                      "published_at": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 20,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/stock/count": {
      "get": {
        "summary": "Count the public stock (same filters as the list)",
        "description": "Same filters as `GET /stock`, minus sort/pagination (unknown keys are simply ignored). Useful for a results-count badge without paging the whole list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "brand",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "gearbox",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "fuel_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "body",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "Car",
                "Truck",
                "Motorcycle",
                "Boat"
              ]
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2100
            },
            "required": false,
            "name": "min_year",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1900,
              "maximum": 2100
            },
            "required": false,
            "name": "max_year",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "min_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "max_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "min_odometer",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "max_odometer",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "created_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "sucursal",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "disponible",
                "reservado",
                "vendido"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "featured",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "{ count }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "count": 7
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/stock/{id}": {
      "get": {
        "summary": "Get one public-stock vehicle",
        "description": "Missing, foreign-tenant and hidden (inactive / deleted / sold) ids all return the same 404. A tenant with `stock_publico.mostrar_vendidos` on serves the ficha of a unit sold in the last 90 days instead of 404ing it, so the card on the grid links somewhere; every other gate (inactive / deleted / merged) still 404s. Same `visit_coordination_required` disclosure rule as the list (ADR 0095): present and `true` only with the tenant opted into `stock_publico.visit_coordination_disclosure` AND an ACTIVE, modalidad=virtual consignment contract on this unit; absent otherwise.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Public stock vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "make": "Suzuki",
                    "model": "Swift",
                    "version": "GL 1.2",
                    "year": 2021,
                    "title": null,
                    "price": {
                      "amount": 9490000,
                      "currency": "CLP"
                    },
                    "odometer": {
                      "value": 31400,
                      "unit": "KM"
                    },
                    "fuel_type": null,
                    "fuel_label": null,
                    "gear_type": null,
                    "gear_label": null,
                    "body_style": null,
                    "body_label": null,
                    "color": null,
                    "doors": null,
                    "displacement_cc": null,
                    "type": "Car",
                    "type_label": "Auto",
                    "merch_label": null,
                    "merch_label_es": null,
                    "is_featured": false,
                    "featured_at": null,
                    "listing_type": "Usado",
                    "status": "disponible",
                    "reserved_at": null,
                    "sold_at": null,
                    "sucursal": {
                      "id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "name": "Sucursal Providencia",
                      "address": null,
                      "address_street": "Av. Nueva Providencia",
                      "address_number": "2214",
                      "address_unit": null,
                      "comuna_code": "13123",
                      "comuna_name": "Providencia",
                      "region_code": "13"
                    },
                    "photos": [],
                    "description": null,
                    "tags": [],
                    "equipment": [],
                    "created_at": "2026-09-20T00:30:01.896Z",
                    "published_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/website-leads": {
      "post": {
        "summary": "Submit a website buyer inquiry (dealer-website write API)",
        "description": "Creates a CRM lead with `website` origin, attaching the inquired Vehículo when `vehicle_id` is given. A proyecto form (Módulo Proyectos) sends `project_id` and/or `unit_model_id` (a tipología names its proyecto): the lead lands on the proyecto, is routed to its ejecutivos, and `message` becomes optional. `attribution` carries the visitor's `first_touch` and `last_touch` (utm_*, gclid, fbclid, anonymous_id); both are kept on the lead. `value_amount` is the lead's value; `quote_amount` is a figure the site quoted that is not the deal (a trade-in quote): it only fills the first-touch template's amount and the inbox card when `value_amount` is absent, and is never stored as the lead's value. Requires the write-only `leads:intake` scope (never `leads:write`). Returns a minimized `{ status }` ack — `received` on create, `duplicate` on dedup — and echoes no internal ids. Rate-limited like every authenticated endpoint; a hidden `hp_website` honeypot field silently drops spam.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 254,
                    "format": "email"
                  },
                  "phone": {
                    "type": "string",
                    "minLength": 5,
                    "maxLength": 40
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  },
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "project_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "unit_model_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "attribution": {
                    "type": "object",
                    "properties": {
                      "first_touch": {
                        "type": "object",
                        "properties": {
                          "utm_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_medium": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_campaign": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_content": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_term": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "fbclid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "gclid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "anonymous_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          }
                        }
                      },
                      "last_touch": {
                        "type": "object",
                        "properties": {
                          "utm_source": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_medium": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_campaign": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_content": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_term": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "utm_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "fbclid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "gclid": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          },
                          "anonymous_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 2000
                          }
                        }
                      }
                    }
                  },
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "value_amount": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1000000000000
                  },
                  "value_currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "quote_amount": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1000000000000
                  },
                  "item_label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "source_ref": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "maxLength": 60
                        },
                        "value": {
                          "type": "string",
                          "maxLength": 300
                        }
                      },
                      "required": [
                        "label",
                        "value"
                      ]
                    },
                    "maxItems": 30
                  },
                  "form_title": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "highlight": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string",
                        "maxLength": 60
                      },
                      "value": {
                        "type": "string",
                        "maxLength": 300
                      }
                    },
                    "required": [
                      "label",
                      "value"
                    ]
                  },
                  "channel_code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "buy",
                      "sell",
                      "financing",
                      "trade_in"
                    ]
                  },
                  "hp_website": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "turnstile_token": {
                    "type": "string",
                    "maxLength": 4096
                  },
                  "consent": {
                    "type": "object",
                    "properties": {
                      "granted": {
                        "type": "boolean"
                      },
                      "source_url": {
                        "type": "string",
                        "maxLength": 2048,
                        "format": "uri"
                      },
                      "text_version": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      }
                    },
                    "required": [
                      "granted",
                      "source_url",
                      "text_version"
                    ]
                  }
                }
              },
              "example": {
                "name": "Camila Reyes",
                "email": "camila.reyes@example.cl",
                "phone": "+56911112222",
                "message": "Hola, ¿el Corolla 2022 sigue disponible?",
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "intent": "buy"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Duplicate inquiry ({ status: \"duplicate\" })",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "duplicate"
                  }
                }
              }
            }
          },
          "201": {
            "description": "Inquiry accepted ({ status: \"received\" })",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "status": "received"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/storefront": {
      "get": {
        "summary": "Storefront bootstrap (channels, published site content + visual identity)",
        "description": "The dealer’s public identity and live contact channels: trading name, whether WhatsApp is connected (and on which number), whether the embeddable webchat is configured, and the active sucursales. Lets a Vitrina-built website decide which CTAs to render — a dealer who connects WhatsApp sees the button appear with no config edit and no redeploy. Readable with the narrow `stock:read` scope; returns only facts a storefront already publishes to the world (no secrets).\n\nTenants holding the `dealer_site` add-on additionally get `sitio` — the PUBLISHED site snapshot, never the draft — whose `sitio.theme` carries the visual identity: `primary_color` (`#rrggbb`), `base` (`light`|`dark`), `display_font`/`body_font` (from the closed whitelist `saira`|`ibmPlexSans`|`poppins`|`nunitoSans`|`archivo`|`montserrat`), `radius` (`sharp`|`rounded`) and `density` (`compact`|`roomy`), plus the OPTIONAL `skin` (`classic`|`luxe`) — which component tree the renderer draws the same content with. `skin` is the one theme field that may be absent: absent means `classic`, the look every site published before the field existed already has, so a stored value the server does not recognise drops the skin alone and leaves the rest of the theme intact — plus `branding` (`logo_url`, and `primary_color` as the fallback for a tenant that has not published a theme). These are RAW identity inputs, not a derived palette: the renderer derives the token set from the single brand colour, contrast-adjusting the text/accent token against the active base so any valid hex yields a WCAG-AA-readable page (vitrina-template ADR 0005). Server-side validation is therefore strict SHAPE validation (hex format, whitelisted font, closed enums) — colours are never silently repainted. Also carries `analytics` (`ga4_measurement_id`, `meta_pixel_id`, ticket 86e2rwqy9) — the dealer’s own GA4/Meta Pixel ids, `null` per field when unconfigured, so the template loads NO third-party script until a valid id is set.\n\nEntitled tenants ALSO get the rest of the dealer’s public identity (ticket 86e2t94ee), so a website can render itself end to end from this one response with no per-client config file: `content` (`tagline`, `hero_image_url`, `city`, `founded_year`, `show_name_with_logo`, `partners[]`, `stats[]`, and `google_rating` — `{ rating, count, url, as_of }` or `null`: the dealer-declared Google score with the date it was last verified; the template hides it once `as_of` is older than 120 days), `contact` (`sales` and optional `service` channels with `label`/`email`/`phone_display`/`phone_e164`/`whatsapp_message`, plus `address` and `maps_url`), `social` (`instagram`/`facebook`/`linkedin`/`tiktok`/`x`/`youtube`), `features` (`blog`, `services`, `consignment`, `catalog_filters`, `financing`, `branches`, `chat`), `trust` (`financing`, `card_payment`, `trade_in`, `insurance`, `digital_transfer`) and `financing` (`annualRate`, `monthlyRatePct`, `caeRefPct`, `terms[]`, `minDownPct`, `defaultDownPct`, `defaultTermMonths`, `legalNote`, `highlight` — `annualRate`/`monthlyRatePct` are the TAN/TEM rate pair, at least one required; `caeRefPct` is the CAE referencial shown with equal prominence per Art. 17 G, Ley 19.496). `content`/`contact`/`features`/`trust` are LIVE tenant config — an edit reaches the site at the next bootstrap window with no Publicar — while `social` and `financing` are projected from the PUBLISHED Sitio snapshot, where the dealer already edits them. Unset feature flags take the platform defaults; unset trust seals are `false`, because a seal is a promise the dealership makes and is never inferred. The `webchat` block additionally carries `welcome_message` and `position` (`br`|`bl`) from the same `settings.webchat` key `GET /widget/config` serves — `webchat.enabled` stays UNGATED (it is a channel fact), only its appearance rides the add-on gate.\n\nAlso carries `publishable_key` (ClickUp 86e2t94e1): a browser-safe `pk_` widget key (ADR 0033) DERIVED on every call from the workspace’s registered storefront domains (`GET /tenant/storefront-domains`), origin-locked to apex + `www.` of each, plus the Vitrina-hosted subdomain. Nothing is stored: adding or removing a domain changes the next value, so a new domain reaches the site at its next bootstrap window with no re-mint and no redeploy. `null` when the workspace has registered no domain — a key with an empty allow-list admits nothing, so returning one would look like success and behave like a 403. `POST /publishable-keys` is unchanged and remains the surface for arbitrary origins and isolated keys. It is NOT gated on `dealer_site`: what gates it is having a domain.\n\nAlso carries `turnstile_site_key`: the Cloudflare Turnstile site key every public form on the site must render, or `null` for \"render no widget\". Resolved from the same pair the intake routes verify with (`POST /widget/consignments`, `/website-leads`, `/widget/appointments`), so the challenge the page shows and the check the server runs cannot drift — a site that renders no widget against a verifying server has every submission refused. Mirrors `GET /widget/config`.turnstileSiteKey.\n\nAlso carries `vende_tu_auto` (#5872): the «Vende tu auto» consignment landing view — path, the Oferta headline and claims, three steps, the Condiciones hint, rating and address — or `null` while the landing is off. Same view as `GET /widget/consignments/landing`.\n\nWithout the add-on, or before the first Publicar, `sitio`/`branding`/`analytics`/`content`/`contact`/`social`/`features`/`trust`/`financing` are `null` and `theme` is absent, and the storefront falls back to its built-in defaults.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "responses": {
          "200": {
            "description": "Storefront capabilities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "name": "Autos del Sur",
                    "whatsapp": {
                      "enabled": true,
                      "phone_e164": null
                    },
                    "webchat": {
                      "enabled": false,
                      "welcome_message": null,
                      "position": null
                    },
                    "sucursales": [
                      {
                        "id": "b1b1b1b1-0000-4000-8000-000000000001",
                        "name": "Sucursal Providencia",
                        "address": null,
                        "address_street": "Av. Nueva Providencia",
                        "address_number": "2214",
                        "address_unit": null,
                        "comuna_code": "13123",
                        "comuna_name": "Providencia",
                        "region_code": "13",
                        "coordinates": null
                      }
                    ],
                    "sitio": null,
                    "branding": null,
                    "analytics": null,
                    "publishable_key": null,
                    "turnstile_site_key": null,
                    "vende_tu_auto": null,
                    "content": null,
                    "contact": null,
                    "social": null,
                    "features": null,
                    "trust": null,
                    "financing": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/storefront/trade-in": {
      "get": {
        "summary": "Buy table for the business’s own quoter (trade-in / buyback)",
        "description": "What the business pays at most for each used model, so its own website quoter reads the same table the agent quotes from instead of a hardcoded copy. Per model: `top` (flawless, «Impecable»), `used` (normal wear, «Con uso»), `direct_purchase` (`false` = taken only as part of the payment for another item, never bought outright) and `min_battery_pct` (a known battery under it is not bought; `null` = no minimum) and `low_battery_factor` (a known battery under `perfect_battery_pct` caps the price at `used × low_battery_factor`; `null` = `used`). Plus `not_bought` (models never bought), `currency` and `perfect_battery_pct` (from this battery health a unit with nothing else wrong counts as flawless). Readable with the storefront’s `stock:read`; the browser twin is `GET /widget/trade-in` with the site’s `pk_` key. `Cache-Control: private, max-age=300` + a strong `ETag` (send `If-None-Match` for a 304). 404 when the workspace keeps no buy table (`PUT /tenant/settings` `trade_in`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "responses": {
          "200": {
            "description": "The public buy table",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "currency": "CLP",
                  "perfect_battery_pct": 90,
                  "models": [
                    {
                      "model": "iPhone 15",
                      "top": 350000,
                      "used": 320000,
                      "direct_purchase": false,
                      "min_battery_pct": null,
                      "low_battery_factor": null
                    },
                    {
                      "model": "iPhone 16 Pro",
                      "top": 600000,
                      "used": 570000,
                      "direct_purchase": true,
                      "min_battery_pct": null,
                      "low_battery_factor": 0.85
                    }
                  ],
                  "not_bought": [
                    "iPhone 13 mini",
                    "iPhone 16e"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/storefront/events": {
      "post": {
        "summary": "Record a storefront traffic/conversion event",
        "description": "Capture one page_view / vehicle_view / lead_submit signal from the tenant's own Vitrina-built storefront. Append-only, no person data — the optional `dedup_key` is an opaque session-ish hint, hashed server-side before storage. Requires the narrow, write-only `storefront_events:write` scope. A bogus or foreign-tenant `vehicle_id` is refused by the underlying composite FK and acked identically to success (no enumeration oracle). Feeds `GET /insights/storefront`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Public Stock"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "page_view",
                      "vehicle_view",
                      "lead_submit"
                    ]
                  },
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "dedup_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "required": [
                  "event"
                ],
                "additionalProperties": false
              },
              "example": {
                "event": "vehicle_view",
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "dedup_key": "sess_8f2c1a"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Event recorded ({ status: \"recorded\" })",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "status": "recorded"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/locations": {
      "get": {
        "summary": "List locations (branches / stores / clinics / offices)",
        "description": "Every location this workspace has, active first. Pass `include_inactive=true` to also see soft-deleted (`is_active: false`) locations — useful for a picker that must still resolve a record filed at a closed location. Readable with `tenant:read` or `marketplace:read`, so a stock integration can resolve the `location_id` it sets and filters vehicles by.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Locations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "include_inactive",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Locations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Sucursal Providencia",
                      "address": null,
                      "address_street": "Av. Nueva Providencia",
                      "address_number": "2214",
                      "address_unit": null,
                      "comuna_code": "13123",
                      "region_code": "13",
                      "address_source": "manual",
                      "phone": "+56229876543",
                      "email": "providencia@example.cl",
                      "manager_name": "Carolina Muñoz",
                      "hours": {
                        "lun_vie": "09:00-19:00",
                        "sab": "10:00-14:00"
                      },
                      "timezone": "America/Santiago",
                      "geo": null,
                      "notes": null,
                      "arrival_info": null,
                      "metadata": {},
                      "is_active": true,
                      "display_seq": 1,
                      "display_id": "B-1",
                      "created_at": "2026-09-15T18:34:09.375Z",
                      "updated_at": "2026-09-15T18:34:26.470Z"
                    },
                    {
                      "id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Sucursal Maipú",
                      "address": null,
                      "address_street": "Av. Pajaritos",
                      "address_number": "3050",
                      "address_unit": null,
                      "comuna_code": "13119",
                      "region_code": "13",
                      "address_source": "manual",
                      "phone": "+56225551212",
                      "email": null,
                      "manager_name": null,
                      "hours": null,
                      "timezone": null,
                      "geo": null,
                      "notes": null,
                      "arrival_info": null,
                      "metadata": {},
                      "is_active": true,
                      "display_seq": 2,
                      "display_id": "B-2",
                      "created_at": "2026-09-15T18:34:09.375Z",
                      "updated_at": "2026-09-15T18:34:26.470Z"
                    },
                    {
                      "id": "b1b1b1b1-0000-4000-8000-000000000003",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Sucursal Vitacura",
                      "address": null,
                      "address_street": null,
                      "address_number": null,
                      "address_unit": null,
                      "comuna_code": "13132",
                      "region_code": "13",
                      "address_source": "manual",
                      "phone": null,
                      "email": null,
                      "manager_name": null,
                      "hours": null,
                      "timezone": null,
                      "geo": null,
                      "notes": null,
                      "arrival_info": null,
                      "metadata": {},
                      "is_active": true,
                      "display_seq": 3,
                      "display_id": "B-3",
                      "created_at": "2026-09-15T18:34:09.375Z",
                      "updated_at": "2026-09-15T18:34:26.470Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a location",
        "description": "The comuna is a code (`comuna_code`, the official 5-digit SUBDERE/INE CUT), never a name — the región is derived from it server-side. Assigns the next `display_id` (`B-1`, `B-2`, …) for this workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Locations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "address_street": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "format": "email"
                  },
                  "manager_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "hours": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {},
                    "description": "Optional per-day override of tenant.settings.business_hours."
                  },
                  "timezone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "geo": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "additionalProperties": false
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  },
                  "arrival_info": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "maps_url": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500,
                        "format": "uri"
                      },
                      "steps": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 500
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 1000,
                              "format": "uri"
                            }
                          },
                          "required": [
                            "text"
                          ],
                          "additionalProperties": false
                        },
                        "maxItems": 10,
                        "default": []
                      }
                    },
                    "additionalProperties": false
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Sucursal Las Condes",
                "address_street": "Av. Apoquindo",
                "address_number": "4500",
                "comuna_code": "13114",
                "phone": "+56221234567",
                "email": "lascondes@example.cl",
                "manager_name": "Felipe Rojas",
                "hours": {
                  "lun_vie": "09:00-19:00",
                  "sab": "10:00-14:00"
                },
                "timezone": "America/Santiago"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Location",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b1b1b1-0000-4000-8000-000000000004",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Sucursal Las Condes",
                    "address": null,
                    "address_street": "Av. Apoquindo",
                    "address_number": "4500",
                    "address_unit": null,
                    "comuna_code": "13114",
                    "region_code": "13",
                    "address_source": "manual",
                    "phone": "+56221234567",
                    "email": "lascondes@example.cl",
                    "manager_name": "Felipe Rojas",
                    "hours": {
                      "lun_vie": "09:00-19:00",
                      "sab": "10:00-14:00"
                    },
                    "timezone": "America/Santiago",
                    "geo": null,
                    "notes": null,
                    "arrival_info": null,
                    "metadata": {},
                    "is_active": true,
                    "display_seq": 4,
                    "display_id": "B-4",
                    "created_at": "2026-09-15T18:34:09.375Z",
                    "updated_at": "2026-09-15T18:34:26.470Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/locations/{id}": {
      "get": {
        "summary": "Get a location (uuid or B- display id)",
        "description": "Accepts the record’s uuid or its `display_id` (`B-1`, `B-2`, …) — a documented lookup shortcut (ADR 0106 §2.5); the response always carries both.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Locations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Location",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Sucursal Providencia",
                    "address": null,
                    "address_street": "Av. Nueva Providencia",
                    "address_number": "2214",
                    "address_unit": null,
                    "comuna_code": "13123",
                    "region_code": "13",
                    "address_source": "manual",
                    "phone": "+56229876543",
                    "email": "providencia@example.cl",
                    "manager_name": "Carolina Muñoz",
                    "hours": {
                      "lun_vie": "09:00-19:00",
                      "sab": "10:00-14:00"
                    },
                    "timezone": "America/Santiago",
                    "geo": null,
                    "notes": null,
                    "arrival_info": null,
                    "metadata": {},
                    "is_active": true,
                    "display_seq": 1,
                    "display_id": "B-1",
                    "created_at": "2026-09-15T18:34:09.375Z",
                    "updated_at": "2026-09-15T18:34:26.470Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Soft-delete a location",
        "description": "Equivalent to `PATCH /locations/{id}` with `{ is_active: false }` — nothing is erased and the location remains resolvable by id or display id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Locations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a location (is_active=false soft-deletes)",
        "description": "Partial update — every field is optional. Setting `is_active: false` is the soft-delete this family uses instead of DELETE for anything already referenced elsewhere (inventory, appointments, records filed against it); the row and its `display_id` stay resolvable, just excluded from the default list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Locations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "address_street": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "format": "email"
                  },
                  "manager_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "hours": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {},
                    "description": "Optional per-day override of tenant.settings.business_hours."
                  },
                  "timezone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "geo": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "lat": {
                        "type": "number"
                      },
                      "lng": {
                        "type": "number"
                      }
                    },
                    "required": [
                      "lat",
                      "lng"
                    ],
                    "additionalProperties": false
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  },
                  "arrival_info": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "maps_url": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500,
                        "format": "uri"
                      },
                      "steps": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 500
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 1000,
                              "format": "uri"
                            }
                          },
                          "required": [
                            "text"
                          ],
                          "additionalProperties": false
                        },
                        "maxItems": 10,
                        "default": []
                      }
                    },
                    "additionalProperties": false
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "is_active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "is_active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Location",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Sucursal Providencia",
                    "address": null,
                    "address_street": "Av. Nueva Providencia",
                    "address_number": "2214",
                    "address_unit": null,
                    "comuna_code": "13123",
                    "region_code": "13",
                    "address_source": "manual",
                    "phone": "+56229876543",
                    "email": "providencia@example.cl",
                    "manager_name": "Carolina Muñoz",
                    "hours": {
                      "lun_vie": "09:00-19:00",
                      "sab": "10:00-14:00"
                    },
                    "timezone": "America/Santiago",
                    "geo": null,
                    "notes": null,
                    "arrival_info": null,
                    "metadata": {},
                    "is_active": false,
                    "display_seq": 1,
                    "display_id": "B-1",
                    "created_at": "2026-09-15T18:34:09.375Z",
                    "updated_at": "2026-09-15T18:34:26.470Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads": {
      "get": {
        "summary": "List leads",
        "description": "The flat list, paged (`page` / `page_size`, max 200) and filterable by board, stage, status, source, intent, owner, team, contact, value band, score band and last activity; `q` matches the title and the contact. `temperature` is a SHORTCUT over `score` (`cold` 0–39 · `warm` 40–69 · `hot` 70–100 · `unscored` score IS NULL) and explicit `min_score` / `max_score` win over it — do not confuse it with the `temperature` FIELD on a lead, which is a judgement somebody recorded about momentum and is not derived from the score. `resource_type` and `resource_id` must be supplied together and narrow to the leads interested in one record. `meta.pagination.total` is the match count, not the page size. A member whose role restricts record visibility sees only the leads they own.\n\n`fields=summary` answers the SAME page with only the identifying and funnel scalars per row — `id`, `display_id`, `title`, `status`, `source`, `intent`, `pipeline_id`, `stage_id`, `contact_id`, `value_amount`, `value_currency`, `owner_user_id`, `score`, `temperature`, `created_at`, `last_activity_at`, `closed_at`, `origin_conversation_id` — plus `stage_name` and `contact_name` when the caller holds the scope that embed rides (`stages:read`, `contacts:read`); a name the caller may not read is ABSENT, never `null`. `meta` is unchanged. A 200-row page is ~525k characters at `full` (the default, byte-identical to omitting the parameter) and a fraction of that at `summary`; page with `summary` to choose, then `GET /leads/{id}` for the ones you need whole.\n\nOn a clinic (healthcare) workspace, `value_amount` on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call `ads_attributed_sales_list`; never sum lead values.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "stage_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "won",
                "lost",
                "unqualified"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "conversation",
                "marketplace",
                "manual",
                "import",
                "ai_agent",
                "chileautos",
                "yapo",
                "mercadolibre",
                "website",
                "facebook_marketplace",
                "form",
                "campaign"
              ]
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "buy",
                "sell",
                "financing",
                "trade_in"
              ]
            },
            "required": false,
            "name": "intent",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "compra",
                "consignacion",
                "unknown"
              ]
            },
            "required": false,
            "name": "acquisition_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "owner_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "team_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "min_value",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "max_value",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 100
            },
            "required": false,
            "name": "min_score",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 100
            },
            "required": false,
            "name": "max_score",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "cold",
                "warm",
                "hot",
                "unscored"
              ]
            },
            "required": false,
            "name": "temperature",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "last_activity_after",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "last_activity_desc",
                "last_activity_asc",
                "created_desc",
                "created_asc",
                "value_desc",
                "value_asc",
                "title_asc",
                "title_desc",
                "score_desc",
                "score_asc",
                "status_asc",
                "status_desc",
                "source_asc",
                "source_desc",
                "stage_asc",
                "stage_desc",
                "contact_asc",
                "contact_desc",
                "expected_close_asc",
                "expected_close_desc"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "vehicle"
              ]
            },
            "required": false,
            "name": "resource_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "resource_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "leads",
                "solicitudes"
              ]
            },
            "required": false,
            "name": "card_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "failed"
              ]
            },
            "required": false,
            "name": "first_touch",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 25
            },
            "required": false,
            "name": "page_size",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "summary",
                "full"
              ],
              "default": "full",
              "description": "`full` (default): every column plus the `contact` / `stage` / `pipeline` / `busqueda` embeds, exactly as without the parameter. `summary`: only the identifying and funnel scalars — `id`, `display_id`, `title`, `status`, `source`, `intent`, `pipeline_id`, `stage_id`, `contact_id`, `value_amount`, `value_currency`, `owner_user_id`, `score`, `temperature`, `created_at`, `last_activity_at`, `closed_at`, `origin_conversation_id`, plus `stage_name` / `contact_name` when the caller may read that embed. Pagination and `meta` are unchanged. Use `summary` to page through many leads cheaply, then `GET /leads/{id}` for the ones you need in full."
            },
            "required": false,
            "description": "`full` (default): every column plus the `contact` / `stage` / `pipeline` / `busqueda` embeds, exactly as without the parameter. `summary`: only the identifying and funnel scalars — `id`, `display_id`, `title`, `status`, `source`, `intent`, `pipeline_id`, `stage_id`, `contact_id`, `value_amount`, `value_currency`, `owner_user_id`, `score`, `temperature`, `created_at`, `last_activity_at`, `closed_at`, `origin_conversation_id`, plus `stage_name` / `contact_name` when the caller may read that embed. Pagination and `meta` are unchanged. Use `summary` to page through many leads cheaply, then `GET /leads/{id}` for the ones you need in full.",
            "name": "fields",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Leads",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "99999999-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "L-89",
                      "display_seq": 89,
                      "title": "María — prueba de manejo Corolla 2022",
                      "notes": null,
                      "status": "open",
                      "source": "conversation",
                      "source_ref": null,
                      "intent": "buy",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "stage_id": "55555555-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "owner_user_id": "11111111-0000-4000-8000-000000000001",
                      "team_id": null,
                      "value_amount": 8990000,
                      "value_currency": "CLP",
                      "value_amount_close_confirmed_at": null,
                      "expected_close_at": "2026-10-15T00:00:00.000Z",
                      "score": 72,
                      "score_reason": "pidió hora para probar el auto y confirmó día",
                      "scored_at": "2026-09-21T14:02:11.000Z",
                      "temperature": "warm",
                      "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                      "temperature_at": "2026-09-21T14:02:11.000Z",
                      "won_lost_reason": null,
                      "lost_reason": null,
                      "lost_reason_source": null,
                      "lost_reason_at": null,
                      "payment_method": "financing",
                      "down_payment_amount": 4000000,
                      "budget_max_amount": 11500000,
                      "has_trade_in": false,
                      "trade_in_description": null,
                      "offer_amount": null,
                      "offer_note": null,
                      "financing_term_months": [
                        24,
                        36
                      ],
                      "installment_amount": null,
                      "max_down_payment_amount": null,
                      "net_income_amount": 1900000,
                      "employer": "CGE",
                      "employment_tenure": "15 años",
                      "budget_currency": null,
                      "down_payment_currency": null,
                      "mortgage_preapproval_status": null,
                      "mortgage_bank": null,
                      "mortgage_preapproved_amount": null,
                      "mortgage_preapproved_currency": null,
                      "meets_rent_income_requirement": null,
                      "has_guarantor": null,
                      "deal_terms_meta": {
                        "payment_method": {
                          "source": "ai",
                          "actor_id": null,
                          "actor_label": null,
                          "at": "2026-09-21T14:02:11.000Z"
                        },
                        "down_payment_amount": {
                          "source": "human",
                          "actor_id": "11111111-0000-4000-8000-000000000001",
                          "actor_label": "Camila Rojas",
                          "at": "2026-09-21T15:10:00.000Z"
                        }
                      },
                      "deal_terms_suggestions": {},
                      "closed_at": null,
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-18T13:44:02.115Z",
                      "updated_at": "2026-09-21T14:02:11.000Z",
                      "last_activity_at": "2026-09-21T14:02:11.000Z",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "María González",
                        "email": "maria.gonzalez@example.cl",
                        "phone": "+56912345001"
                      },
                      "stage": {
                        "id": "55555555-0000-4000-8000-000000000001",
                        "name": "Nuevo",
                        "slug": "new",
                        "position": 0,
                        "won_state": null,
                        "pipeline_id": "44444444-0000-4000-8000-000000000001"
                      },
                      "pipeline": {
                        "id": "44444444-0000-4000-8000-000000000001",
                        "kind": "sales",
                        "name": "Ventas"
                      }
                    },
                    {
                      "id": "99999999-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "L-90",
                      "display_seq": 90,
                      "title": "Consulta por financiamiento — Ana Soto",
                      "notes": null,
                      "status": "open",
                      "source": "website",
                      "source_ref": null,
                      "intent": "financing",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001",
                      "stage_id": "55555555-0000-4000-8000-000000000002",
                      "conversation_id": null,
                      "origin_conversation_id": null,
                      "owner_user_id": null,
                      "team_id": null,
                      "value_amount": null,
                      "value_currency": "CLP",
                      "value_amount_close_confirmed_at": null,
                      "expected_close_at": null,
                      "score": null,
                      "score_reason": null,
                      "scored_at": null,
                      "temperature": null,
                      "temperature_reason": null,
                      "temperature_at": null,
                      "won_lost_reason": null,
                      "lost_reason": null,
                      "lost_reason_source": null,
                      "lost_reason_at": null,
                      "payment_method": "financing",
                      "down_payment_amount": 4000000,
                      "budget_max_amount": 11500000,
                      "has_trade_in": false,
                      "trade_in_description": null,
                      "offer_amount": null,
                      "offer_note": null,
                      "financing_term_months": [
                        24,
                        36
                      ],
                      "installment_amount": null,
                      "max_down_payment_amount": null,
                      "net_income_amount": 1900000,
                      "employer": "CGE",
                      "employment_tenure": "15 años",
                      "budget_currency": null,
                      "down_payment_currency": null,
                      "mortgage_preapproval_status": null,
                      "mortgage_bank": null,
                      "mortgage_preapproved_amount": null,
                      "mortgage_preapproved_currency": null,
                      "meets_rent_income_requirement": null,
                      "has_guarantor": null,
                      "deal_terms_meta": {
                        "payment_method": {
                          "source": "ai",
                          "actor_id": null,
                          "actor_label": null,
                          "at": "2026-09-21T14:02:11.000Z"
                        },
                        "down_payment_amount": {
                          "source": "human",
                          "actor_id": "11111111-0000-4000-8000-000000000001",
                          "actor_label": "Camila Rojas",
                          "at": "2026-09-21T15:10:00.000Z"
                        }
                      },
                      "deal_terms_suggestions": {},
                      "closed_at": null,
                      "created_by": null,
                      "created_at": "2026-09-19T09:12:44.000Z",
                      "updated_at": "2026-09-19T09:12:44.000Z",
                      "last_activity_at": "2026-09-19T09:12:44.000Z",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "Ana Soto",
                        "email": null,
                        "phone": "+56912345001"
                      },
                      "stage": {
                        "id": "55555555-0000-4000-8000-000000000002",
                        "name": "Contactado",
                        "slug": "contacted",
                        "position": 1,
                        "won_state": null,
                        "pipeline_id": "44444444-0000-4000-8000-000000000001"
                      },
                      "pipeline": {
                        "id": "44444444-0000-4000-8000-000000000001",
                        "kind": "sales",
                        "name": "Ventas"
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 16,
                      "limit": 25
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a lead",
        "description": "Opens an opportunity for an existing contact. `pipeline_id` defaults to the workspace's fallback SALES board (a lead cannot live on a ticket or vehicle board — 400 if you name one) and `stage_id` to that board's first column. `source` says WHERE it came from, `intent` says WHY (`buy` · `sell` · `financing` · `trade_in`, default `buy`); both are set at creation and are not part of the update body. With no `owner_user_id` but a `team_id`, the team's rotation picks the owner. Emits `lead.created`. Money is `value_amount` + `value_currency` and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down `by_currency` and flag `is_mixed_currency` rather than summing across them.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "conversation",
                      "marketplace",
                      "manual",
                      "import",
                      "ai_agent",
                      "chileautos",
                      "yapo",
                      "mercadolibre",
                      "website",
                      "facebook_marketplace",
                      "campaign"
                    ]
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "buy",
                      "sell",
                      "financing",
                      "trade_in"
                    ]
                  },
                  "acquisition_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "compra",
                      "consignacion",
                      "unknown"
                    ],
                    "description": "«Compra y consignación»: compra (direct purchase) | consignacion | unknown. Set on intake when known; reaching the «Comprado» / «Consignado» stage stamps it and overwrites an earlier value. Read back as null on a lead that was never on that board."
                  },
                  "source_ref": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  },
                  "expected_close_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "score": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "contact_id",
                  "title"
                ]
              },
              "example": {
                "contact_id": "22222222-0000-4000-8000-000000000001",
                "pipeline_id": "44444444-0000-4000-8000-000000000001",
                "title": "María — prueba de manejo Corolla 2022",
                "source": "manual",
                "intent": "buy",
                "value_amount": 8990000,
                "value_currency": "CLP",
                "expected_close_at": "2026-10-15T00:00:00.000Z",
                "owner_user_id": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/leads/from-conversation": {
      "post": {
        "summary": "Convert a conversation into a lead",
        "description": "Opens an opportunity out of a conversation that already has a contact: the contact is resolved from the thread (404 when it has none), the conversation becomes the lead's ORIGIN and its primary link, and a lead opened on a thread somebody already owns inherits that owner unless you name one. Emits `lead.created` with `source: conversation`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 20000
                  },
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  }
                },
                "required": [
                  "conversation_id"
                ]
              },
              "example": {
                "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                "title": "María — prueba de manejo Corolla 2022",
                "pipeline_id": "44444444-0000-4000-8000-000000000001",
                "owner_user_id": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/leads/import": {
      "post": {
        "summary": "Bulk-import leads",
        "description": "Up to 2 000 rows in one call — a trade-show list, a spreadsheet, a migration from another system. Each row is matched to an existing contact by `contact_email` or `contact_phone`, and to a stage by `stage_slug` / an owner by `owner_email`. PARTIAL SUCCESS IS THE NORMAL OUTCOME and the status is 200 either way: the report says how many rows landed and gives a per-row `ok` with the `lead_id` or the reason it failed. Imported leads skip owner rotation — the file decides the owner.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "contact_email": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "email"
                        },
                        "contact_phone": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "minLength": 1,
                          "maxLength": 64
                        },
                        "title": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 500
                        },
                        "value_amount": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "minLength": 1,
                          "maxLength": 8
                        },
                        "expected_close_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "owner_email": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "email"
                        },
                        "stage_slug": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "source": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "minLength": 1,
                          "maxLength": 64
                        }
                      },
                      "required": [
                        "title"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 2000
                  }
                },
                "required": [
                  "rows"
                ]
              },
              "example": {
                "rows": [
                  {
                    "title": "Consulta feria de autos — stand 12",
                    "contact_email": "j.morales@example.cl",
                    "value_amount": 7500000,
                    "currency": "CLP",
                    "stage_slug": "new",
                    "source": "import"
                  },
                  {
                    "title": "Consulta feria de autos — stand 14",
                    "contact_phone": "+56988887777",
                    "owner_email": "camila@example.cl"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Import report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 2,
                    "inserted": 1,
                    "failed": 1,
                    "rows": [
                      {
                        "row": 1,
                        "ok": true,
                        "lead_id": "99999999-0000-4000-8000-000000000001"
                      },
                      {
                        "row": 2,
                        "ok": false,
                        "error": "No contact matched +56988887777"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/leads/summary": {
      "get": {
        "summary": "Lead pipeline summary",
        "description": "How many leads are open, won, lost and unqualified, and what they are worth. Optionally narrowed to one board with `pipeline_id`. Money is `value_amount` + `value_currency` and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down `by_currency` and flag `is_mixed_currency` rather than summing across them. On a clinic (healthcare) workspace, `value_amount` on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call `ads_attributed_sales_list`; never sum lead values.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "pipeline_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "open": 13,
                    "won": 2,
                    "lost": 1,
                    "unqualified": 0,
                    "total_value_open": 194077750,
                    "total_value_won": 2499000,
                    "total_value_open_by_currency": {
                      "CLP": 193990000,
                      "USD": 42750
                    },
                    "total_value_won_by_currency": {
                      "CLP": 2499000
                    },
                    "is_mixed_currency": true,
                    "is_mixed_currency_open": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/stats": {
      "get": {
        "summary": "Lead score bands",
        "description": "The count in each SCORE band — `cold` 0–39, `warm` 40–69, `hot` 70–100, `unscored` — plus the average score. Bands over `lead.score`, not over the recorded `temperature` judgement; the two are different facts about a lead and neither is derived from the other.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "pipeline_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 16,
                    "hot": 2,
                    "warm": 0,
                    "cold": 8,
                    "unscored": 6,
                    "avg_score": 28.5
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/funnel": {
      "get": {
        "summary": "Lead funnel by stage",
        "description": "One row per stage of a board, in position order: how many OPEN leads sit there, what they are worth, and the MEDIAN hours a lead has been sitting on that stage — the number that says where the funnel is stuck. Money is `value_amount` + `value_currency` and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down `by_currency` and flag `is_mixed_currency` rather than summing across them.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "leads",
                "solicitudes"
              ]
            },
            "required": false,
            "name": "card_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "failed"
              ]
            },
            "required": false,
            "name": "first_touch",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Funnel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "stage_id": "55555555-0000-4000-8000-000000000001",
                      "stage_name": "Nuevo",
                      "stage_slug": "new",
                      "position": 0,
                      "open_count": 6,
                      "total_value": 193990000,
                      "total_value_by_currency": {
                        "CLP": 193990000
                      },
                      "is_mixed_currency": false,
                      "median_time_in_stage_hours": 350.91
                    },
                    {
                      "stage_id": "55555555-0000-4000-8000-000000000002",
                      "stage_name": "Contactado",
                      "stage_slug": "contacted",
                      "position": 1,
                      "open_count": 2,
                      "total_value": 15450000,
                      "total_value_by_currency": {
                        "CLP": 15450000
                      },
                      "is_mixed_currency": false,
                      "median_time_in_stage_hours": 427.53
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/kanban": {
      "get": {
        "summary": "Leads grouped by stage (board view)",
        "description": "The board itself: every stage with its leads already nested, up to 200 per column — one request instead of one per column. The cards carry the same embeds and the same scope gating as `GET /leads`. On a clinic (healthcare) workspace, `value_amount` on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call `ads_attributed_sales_list`; never sum lead values.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "pipeline_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "leads",
                "solicitudes"
              ]
            },
            "required": false,
            "name": "card_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "failed"
              ]
            },
            "required": false,
            "name": "first_touch",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "first_touch_to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Kanban",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "stages": [
                      {
                        "id": "55555555-0000-4000-8000-000000000001",
                        "name": "Nuevo",
                        "slug": "new",
                        "position": 0,
                        "won_state": null,
                        "is_terminal": false,
                        "leads": [
                          {
                            "id": "99999999-0000-4000-8000-000000000001",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "display_id": "L-89",
                            "display_seq": 89,
                            "title": "María — prueba de manejo Corolla 2022",
                            "notes": null,
                            "status": "open",
                            "source": "conversation",
                            "source_ref": null,
                            "intent": "buy",
                            "contact_id": "22222222-0000-4000-8000-000000000001",
                            "pipeline_id": "44444444-0000-4000-8000-000000000001",
                            "stage_id": "55555555-0000-4000-8000-000000000001",
                            "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                            "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                            "owner_user_id": "11111111-0000-4000-8000-000000000001",
                            "team_id": null,
                            "value_amount": 8990000,
                            "value_currency": "CLP",
                            "value_amount_close_confirmed_at": null,
                            "expected_close_at": "2026-10-15T00:00:00.000Z",
                            "score": 72,
                            "score_reason": "pidió hora para probar el auto y confirmó día",
                            "scored_at": "2026-09-21T14:02:11.000Z",
                            "temperature": "warm",
                            "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                            "temperature_at": "2026-09-21T14:02:11.000Z",
                            "won_lost_reason": null,
                            "lost_reason": null,
                            "lost_reason_source": null,
                            "lost_reason_at": null,
                            "payment_method": "financing",
                            "down_payment_amount": 4000000,
                            "budget_max_amount": 11500000,
                            "has_trade_in": false,
                            "trade_in_description": null,
                            "offer_amount": null,
                            "offer_note": null,
                            "financing_term_months": [
                              24,
                              36
                            ],
                            "installment_amount": null,
                            "max_down_payment_amount": null,
                            "net_income_amount": 1900000,
                            "employer": "CGE",
                            "employment_tenure": "15 años",
                            "budget_currency": null,
                            "down_payment_currency": null,
                            "mortgage_preapproval_status": null,
                            "mortgage_bank": null,
                            "mortgage_preapproved_amount": null,
                            "mortgage_preapproved_currency": null,
                            "meets_rent_income_requirement": null,
                            "has_guarantor": null,
                            "deal_terms_meta": {
                              "payment_method": {
                                "source": "ai",
                                "actor_id": null,
                                "actor_label": null,
                                "at": "2026-09-21T14:02:11.000Z"
                              },
                              "down_payment_amount": {
                                "source": "human",
                                "actor_id": "11111111-0000-4000-8000-000000000001",
                                "actor_label": "Camila Rojas",
                                "at": "2026-09-21T15:10:00.000Z"
                              }
                            },
                            "deal_terms_suggestions": {},
                            "closed_at": null,
                            "created_by": "11111111-0000-4000-8000-000000000001",
                            "created_at": "2026-09-18T13:44:02.115Z",
                            "updated_at": "2026-09-21T14:02:11.000Z",
                            "last_activity_at": "2026-09-21T14:02:11.000Z",
                            "contact": {
                              "id": "22222222-0000-4000-8000-000000000001",
                              "name": "María González",
                              "email": "maria.gonzalez@example.cl",
                              "phone": "+56912345001"
                            },
                            "stage": {
                              "id": "55555555-0000-4000-8000-000000000001",
                              "name": "Nuevo",
                              "slug": "new",
                              "position": 0,
                              "won_state": null,
                              "pipeline_id": "44444444-0000-4000-8000-000000000001"
                            },
                            "pipeline": {
                              "id": "44444444-0000-4000-8000-000000000001",
                              "kind": "sales",
                              "name": "Ventas"
                            }
                          }
                        ]
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000002",
                        "name": "Contactado",
                        "slug": "contacted",
                        "position": 1,
                        "won_state": null,
                        "is_terminal": false,
                        "leads": [
                          {
                            "id": "99999999-0000-4000-8000-000000000002",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "display_id": "L-90",
                            "display_seq": 90,
                            "title": "Consulta por financiamiento — Ana Soto",
                            "notes": null,
                            "status": "open",
                            "source": "website",
                            "source_ref": null,
                            "intent": "financing",
                            "contact_id": "22222222-0000-4000-8000-000000000001",
                            "pipeline_id": "44444444-0000-4000-8000-000000000001",
                            "stage_id": "55555555-0000-4000-8000-000000000002",
                            "conversation_id": null,
                            "origin_conversation_id": null,
                            "owner_user_id": null,
                            "team_id": null,
                            "value_amount": null,
                            "value_currency": "CLP",
                            "value_amount_close_confirmed_at": null,
                            "expected_close_at": null,
                            "score": null,
                            "score_reason": null,
                            "scored_at": null,
                            "temperature": null,
                            "temperature_reason": null,
                            "temperature_at": null,
                            "won_lost_reason": null,
                            "lost_reason": null,
                            "lost_reason_source": null,
                            "lost_reason_at": null,
                            "payment_method": "financing",
                            "down_payment_amount": 4000000,
                            "budget_max_amount": 11500000,
                            "has_trade_in": false,
                            "trade_in_description": null,
                            "offer_amount": null,
                            "offer_note": null,
                            "financing_term_months": [
                              24,
                              36
                            ],
                            "installment_amount": null,
                            "max_down_payment_amount": null,
                            "net_income_amount": 1900000,
                            "employer": "CGE",
                            "employment_tenure": "15 años",
                            "budget_currency": null,
                            "down_payment_currency": null,
                            "mortgage_preapproval_status": null,
                            "mortgage_bank": null,
                            "mortgage_preapproved_amount": null,
                            "mortgage_preapproved_currency": null,
                            "meets_rent_income_requirement": null,
                            "has_guarantor": null,
                            "deal_terms_meta": {
                              "payment_method": {
                                "source": "ai",
                                "actor_id": null,
                                "actor_label": null,
                                "at": "2026-09-21T14:02:11.000Z"
                              },
                              "down_payment_amount": {
                                "source": "human",
                                "actor_id": "11111111-0000-4000-8000-000000000001",
                                "actor_label": "Camila Rojas",
                                "at": "2026-09-21T15:10:00.000Z"
                              }
                            },
                            "deal_terms_suggestions": {},
                            "closed_at": null,
                            "created_by": null,
                            "created_at": "2026-09-19T09:12:44.000Z",
                            "updated_at": "2026-09-19T09:12:44.000Z",
                            "last_activity_at": "2026-09-19T09:12:44.000Z",
                            "contact": {
                              "id": "22222222-0000-4000-8000-000000000001",
                              "name": "Ana Soto",
                              "email": null,
                              "phone": "+56912345001"
                            },
                            "stage": {
                              "id": "55555555-0000-4000-8000-000000000002",
                              "name": "Contactado",
                              "slug": "contacted",
                              "position": 1,
                              "won_state": null,
                              "pipeline_id": "44444444-0000-4000-8000-000000000001"
                            },
                            "pipeline": {
                              "id": "44444444-0000-4000-8000-000000000001",
                              "kind": "sales",
                              "name": "Ventas"
                            }
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/win-rate/{dimension}": {
      "get": {
        "summary": "Win rate by owner, team or source",
        "description": "Won / (won + lost) grouped by `owner_user_id`, `team_id` or `source`. UNQUALIFIED leads are excluded from both sides on purpose: they were never a sale to win or lose, and counting them as losses would punish whoever is best at qualifying early. `key` is the owner or team uuid, or the source name — and the literal string `unassigned` for the bucket of closed leads that carried no owner or team at all.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "owner_user_id",
                "team_id",
                "source"
              ]
            },
            "required": true,
            "name": "dimension",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "pipeline_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Win rate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "dimension": "owner_user_id",
                      "key": "11111111-0000-4000-8000-000000000001",
                      "won": 2,
                      "lost": 1,
                      "win_rate": 0.6666666666666666
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}": {
      "get": {
        "summary": "Get a lead",
        "description": "One opportunity, with its contact, stage and pipeline embedded. On a clinic workspace it also carries `origin`: the Origen of its contact, Meta (with the ad), Orgánico (a linked contact with no ad touch) or Sin dato (no contact) — the attribution Vitrina Ads reports. On a clinic (healthcare) workspace, `value_amount` on a WON lead is cash the patient paid, credited to that lead; before won it is the open presupuesto (a quote), not a sale. For return, ROAS, paid per ad or which ad brought paying patients, call `ads_attributed_sales_list`; never sum lead values. `{id}` accepts the lead uuid or its ID visible (`L-89`) — the label is a lookup shortcut on the path only, never a value to store.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a lead",
        "description": "Only the fields present in the body are written; at least one is required. `source` and `intent` are set at creation and are not editable, and the stage is moved with `PUT /leads/{id}/stage` rather than here. `temperature` is the recorded JUDGEMENT about momentum (`hot` · `warm` · `cold`): omitting it leaves it alone, sending `null` RETRACTS it — which is how a person undoes a wrong verdict. Setting `owner_user_id` reassigns the lead's conversations with it. Money is `value_amount` + `value_currency` and the pair travels together: a workspace may work in more than one currency, so the analytics endpoints break their totals down `by_currency` and flag `is_mixed_currency` rather than summing across them.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  },
                  "expected_close_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "score": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "temperature": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "hot",
                      "warm",
                      "cold"
                    ]
                  },
                  "temperature_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "lost_reason": {
                    "type": "string",
                    "enum": [
                      "bought_elsewhere",
                      "financing_rejected",
                      "unit_unavailable",
                      "price",
                      "not_ready",
                      "unresponsive",
                      "not_interested",
                      "other"
                    ]
                  },
                  "acquisition_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "compra",
                      "consignacion",
                      "unknown"
                    ],
                    "description": "«Compra y consignación»: compra (direct purchase) | consignacion | unknown. Set on intake when known; reaching the «Comprado» / «Consignado» stage stamps it and overwrites an earlier value. Read back as null on a lead that was never on that board."
                  }
                }
              },
              "example": {
                "value_amount": 8750000,
                "expected_close_at": "2026-10-31T00:00:00.000Z",
                "temperature": "hot",
                "temperature_reason": "volvió a escribir pidiendo la reserva"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8750000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-31T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "hot",
                    "temperature_reason": "volvió a escribir pidiendo la reserva",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-22T08:39:20.206Z",
                    "last_activity_at": "2026-09-22T08:39:20.206Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a lead",
        "description": "Removes the opportunity and its history. The CONTACT is untouched — a deleted lead is an opportunity that should never have been opened, not a person who should be forgotten. A lead that really ended belongs on `/lost` or `/unqualify`, which keep the record and the reason.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/leads/{id}/activity": {
      "get": {
        "summary": "List a lead's activity timeline",
        "description": "Newest first. Every line carries its `author` (ADR 0106 §3.1): the member who acted (with `via` when they acted through a connected app or a personal token — «Camila vía Claude»), the API key that acted («CRM propio (API)», named as it was then), the AI agent, or the system. `kind` says what happened and `data` is kind-specific; `id` is the timeline row's own legacy sequence number, not a uuid — `lead_id` beside it is the reference.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Activity lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LeadActivity"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": 474,
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": "99999999-0000-4000-8000-000000000001",
                      "kind": "stage_changed",
                      "actor_user_id": "11111111-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "data": {
                        "reason": "la llamamos y confirmó la hora",
                        "from_stage_id": "55555555-0000-4000-8000-000000000001",
                        "to_stage_id": "55555555-0000-4000-8000-000000000002",
                        "from_stage_slug": "new",
                        "to_stage_slug": "contacted",
                        "from_stage_name": "Nuevo",
                        "to_stage_name": "Contactado",
                        "to_stage_category": "open"
                      },
                      "author": {
                        "kind": "member",
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Camila Rojas",
                        "via": {
                          "kind": "connected_app",
                          "name": "Claude"
                        }
                      },
                      "created_at": "2026-09-22T08:39:25.338Z"
                    },
                    {
                      "id": 473,
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": "99999999-0000-4000-8000-000000000001",
                      "kind": "assigned",
                      "actor_user_id": null,
                      "conversation_id": null,
                      "data": {
                        "from_owner_user_id": null,
                        "to_owner_user_id": "11111111-0000-4000-8000-000000000001"
                      },
                      "author": {
                        "kind": "api_key",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM propio"
                      },
                      "created_at": "2026-09-21T14:02:11.000Z"
                    },
                    {
                      "id": 470,
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": "99999999-0000-4000-8000-000000000001",
                      "kind": "created",
                      "actor_user_id": null,
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "data": {
                        "source": "conversation",
                        "title": "María — prueba de manejo Corolla 2022"
                      },
                      "author": {
                        "kind": "system",
                        "id": null,
                        "name": null
                      },
                      "created_at": "2026-09-18T13:44:02.115Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/deal-terms": {
      "put": {
        "summary": "Set a lead's deal terms (human)",
        "description": "Deal terms are twenty-one nullable facts: `payment_method` (`cash` | `financing` | `credit_card` | `debit_card` | `mixed`), `down_payment_amount`, `budget_max_amount`, `offer_amount` (in the lead's `value_currency`), `has_trade_in` (null = unknown, false = none), `trade_in_description`, `offer_note`, and the financing ones: `financing_term_months` (plazo(s) = number of cuotas, e.g. `[12, 18, 24]`), `installment_amount` (valor cuota), `max_down_payment_amount` (pie máximo), `net_income_amount` (renta líquida), `employer`, `employment_tenure`, and the real-estate ones: `budget_currency` / `down_payment_currency` / `mortgage_preapproved_currency` (`UF` | `CLP`, the currency the amount was stated in; null = the lead's `value_currency`), `mortgage_preapproval_status` (`not_started` | `in_progress` | `pre_approved` | `rejected`), `mortgage_bank`, `mortgage_preapproved_amount`, `meets_rent_income_requirement` and `has_guarantor` (aval) for an arriendo. Each carries provenance in `deal_terms_meta` (`ai` | `human`); an AI writer never overwrites a human-owned field — a disagreeing AI reading is kept in `deal_terms_suggestions` instead. Every field in the body becomes human-owned (`source: human`, the caller as actor) and its pending suggestion is removed; `null` clears a value but keeps it human-owned, so the next AI read proposes instead of re-filling it. Accepting a suggestion is this call with the suggested value. `applicant_full_name` / `applicant_rut` are the applicant's identity and are written to the lead's PRIMARY contact (`legal_name`, `tax_id` kind `rut`) — no provenance; 400 when the lead has no contact or the RUT check digit is wrong, 409 when another contact already holds that RUT. Changes land on the lead's timeline as `deal_terms_changed`. Returns the Oportunidad `lead` object. `{id}` accepts the lead uuid or its ID visible (`L-89`) — the label is a lookup shortcut on the path only, never a value to store.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "payment_method": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "cash",
                      "financing",
                      "credit_card",
                      "debit_card",
                      "mixed"
                    ]
                  },
                  "down_payment_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "budget_max_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "has_trade_in": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "trade_in_description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "offer_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "offer_note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "financing_term_months": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 120
                    },
                    "minItems": 1,
                    "maxItems": 6,
                    "description": "Plazo(s) in months the customer asked to quote — the number of cuotas. Stored deduped and ascending, e.g. [12, 18, 24]."
                  },
                  "installment_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Valor de la cuota as quoted by the dealership / financiera."
                  },
                  "max_down_payment_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "The most pie the customer could put if needed."
                  },
                  "net_income_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Renta líquida mensual."
                  },
                  "employer": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 300,
                    "description": "Company or institution the customer works for («CGE»)."
                  },
                  "employment_tenure": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 300,
                    "description": "Antigüedad laboral in approximate whole years («8 años»)."
                  },
                  "budget_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Currency of `budget_max_amount` (UF | CLP). null = the lead `value_currency`. A UF amount keeps up to 2 decimals (62.5); any other currency is rounded to a whole number."
                  },
                  "down_payment_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Currency of the pie (`down_payment_amount`, `max_down_payment_amount`). null = the lead `value_currency`. A UF amount keeps up to 2 decimals (62.5); any other currency is rounded to a whole number."
                  },
                  "mortgage_preapproval_status": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "not_started",
                      "in_progress",
                      "pre_approved",
                      "rejected"
                    ],
                    "description": "Crédito hipotecario: not_started (no lo ha pedido) | in_progress (en evaluación) | pre_approved | rejected."
                  },
                  "mortgage_bank": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 300,
                    "description": "Bank evaluating or pre-approving the crédito hipotecario («BancoEstado»)."
                  },
                  "mortgage_preapproved_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "Amount the bank pre-approved, in `mortgage_preapproved_currency`."
                  },
                  "mortgage_preapproved_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Currency of `mortgage_preapproved_amount` (UF | CLP). A UF amount keeps up to 2 decimals; any other currency is rounded to a whole number."
                  },
                  "meets_rent_income_requirement": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Arriendo: does the applicant meet the house's renta requirement? null = unknown."
                  },
                  "has_guarantor": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "description": "Arriendo: does the applicant have an aval? null = unknown."
                  },
                  "applicant_full_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The applicant's full legal name → the lead's primary contact `legal_name`. null clears."
                  },
                  "applicant_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 20,
                    "description": "The applicant's RUT in any spelling → the primary contact's `tax_id` (kind `rut`), stored canonical. 400 on a wrong check digit, 409 when another contact of the workspace already holds it. null clears."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "payment_method": "financing",
                "financing_term_months": [
                  12,
                  18,
                  24
                ],
                "net_income_amount": 1900000,
                "applicant_rut": "12.345.678-5"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Oportunidad lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "99999999-0000-4000-8000-000000000001",
                  "display_id": "L-89",
                  "title": "María — prueba de manejo Corolla 2022",
                  "status": "open",
                  "source": "mercadolibre",
                  "stage": {
                    "id": "55555555-0000-4000-8000-000000000001",
                    "name": "Contactado"
                  },
                  "pipeline": {
                    "id": "44444444-0000-4000-8000-000000000001",
                    "name": "Venta de vehículos"
                  },
                  "score": 72,
                  "score_reason": "pidió hora para probar el auto y confirmó día",
                  "scored_at": "2026-09-21T14:02:11.000Z",
                  "facts_changed_at": "2026-09-21T13:40:00.000Z",
                  "temperature": "warm",
                  "value_currency": "CLP",
                  "deal_terms": {
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": true,
                    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
                    "offer_amount": 10500000,
                    "offer_note": "Financiado · pendiente de aprobación",
                    "financing_term_months": [
                      12,
                      18,
                      24
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": 5000000,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null
                  },
                  "deal_terms_meta": {
                    "payment_method": {
                      "source": "ai",
                      "actor_id": null,
                      "actor_label": null,
                      "at": "2026-09-21T14:02:11.000Z"
                    },
                    "offer_amount": {
                      "source": "human",
                      "actor_id": "11111111-0000-4000-8000-000000000001",
                      "actor_label": "Camila Rojas",
                      "at": "2026-09-21T15:10:00.000Z"
                    }
                  },
                  "deal_terms_suggestions": {
                    "offer_amount": {
                      "value": 10800000,
                      "reason": "subió la oferta a 10,8 millones",
                      "at": "2026-09-22T10:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/deal-terms/suggestions/{field}": {
      "delete": {
        "summary": "Dismiss an AI deal-term suggestion (keep the human value)",
        "description": "«Mantener la mía»: removes the pending AI suggestion for one field and leaves the human value as it is. Idempotent — no suggestion is a no-op. Returns the Oportunidad `lead` object. `{id}` accepts the lead uuid or its ID visible (`L-89`) — the label is a lookup shortcut on the path only, never a value to store.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "payment_method",
                "down_payment_amount",
                "budget_max_amount",
                "has_trade_in",
                "trade_in_description",
                "offer_amount",
                "offer_note",
                "financing_term_months",
                "installment_amount",
                "max_down_payment_amount",
                "net_income_amount",
                "employer",
                "employment_tenure",
                "budget_currency",
                "down_payment_currency",
                "mortgage_preapproval_status",
                "mortgage_bank",
                "mortgage_preapproved_amount",
                "mortgage_preapproved_currency",
                "meets_rent_income_requirement",
                "has_guarantor"
              ]
            },
            "required": true,
            "name": "field",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Oportunidad lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "99999999-0000-4000-8000-000000000001",
                  "display_id": "L-89",
                  "title": "María — prueba de manejo Corolla 2022",
                  "status": "open",
                  "source": "mercadolibre",
                  "stage": {
                    "id": "55555555-0000-4000-8000-000000000001",
                    "name": "Contactado"
                  },
                  "pipeline": {
                    "id": "44444444-0000-4000-8000-000000000001",
                    "name": "Venta de vehículos"
                  },
                  "score": 72,
                  "score_reason": "pidió hora para probar el auto y confirmó día",
                  "scored_at": "2026-09-21T14:02:11.000Z",
                  "facts_changed_at": "2026-09-21T13:40:00.000Z",
                  "temperature": "warm",
                  "value_currency": "CLP",
                  "deal_terms": {
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": true,
                    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
                    "offer_amount": 10500000,
                    "offer_note": "Financiado · pendiente de aprobación",
                    "financing_term_months": [
                      12,
                      18,
                      24
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": 5000000,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null
                  },
                  "deal_terms_meta": {
                    "payment_method": {
                      "source": "ai",
                      "actor_id": null,
                      "actor_label": null,
                      "at": "2026-09-21T14:02:11.000Z"
                    },
                    "offer_amount": {
                      "source": "human",
                      "actor_id": "11111111-0000-4000-8000-000000000001",
                      "actor_label": "Camila Rojas",
                      "at": "2026-09-21T15:10:00.000Z"
                    }
                  },
                  "deal_terms_suggestions": {
                    "offer_amount": {
                      "value": 10800000,
                      "reason": "subió la oferta a 10,8 millones",
                      "at": "2026-09-22T10:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/review": {
      "post": {
        "summary": "Run the lead reviewer on this lead now",
        "description": "Synchronous (typically 5–20 s): the same lead-reviewer pass the background worker runs — score, temperature, the stage ladder (auto move or suggestion) and the deal terms the conversation states. Returns the Oportunidad `lead` object. At most once per lead per 60 s: a repeat answers 429 with `error.retry_after_seconds` (and a `Retry-After` header). 409 when lead review is off for the workspace or the lead is closed; 502 when the review could not complete. `{id}` accepts the lead uuid or its ID visible (`L-89`) — the label is a lookup shortcut on the path only, never a value to store.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Oportunidad lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "99999999-0000-4000-8000-000000000001",
                  "display_id": "L-89",
                  "title": "María — prueba de manejo Corolla 2022",
                  "status": "open",
                  "source": "mercadolibre",
                  "stage": {
                    "id": "55555555-0000-4000-8000-000000000001",
                    "name": "Contactado"
                  },
                  "pipeline": {
                    "id": "44444444-0000-4000-8000-000000000001",
                    "name": "Venta de vehículos"
                  },
                  "score": 72,
                  "score_reason": "pidió hora para probar el auto y confirmó día",
                  "scored_at": "2026-09-21T14:02:11.000Z",
                  "facts_changed_at": "2026-09-21T13:40:00.000Z",
                  "temperature": "warm",
                  "value_currency": "CLP",
                  "deal_terms": {
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": true,
                    "trade_in_description": "Chery Tiggo 2 Pro 2023, 32.000 km",
                    "offer_amount": 10500000,
                    "offer_note": "Financiado · pendiente de aprobación",
                    "financing_term_months": [
                      12,
                      18,
                      24
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": 5000000,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null
                  },
                  "deal_terms_meta": {
                    "payment_method": {
                      "source": "ai",
                      "actor_id": null,
                      "actor_label": null,
                      "at": "2026-09-21T14:02:11.000Z"
                    },
                    "offer_amount": {
                      "source": "human",
                      "actor_id": "11111111-0000-4000-8000-000000000001",
                      "actor_label": "Camila Rojas",
                      "at": "2026-09-21T15:10:00.000Z"
                    }
                  },
                  "deal_terms_suggestions": {
                    "offer_amount": {
                      "value": 10800000,
                      "reason": "subió la oferta a 10,8 millones",
                      "at": "2026-09-22T10:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/stage": {
      "put": {
        "summary": "Move a lead to another stage",
        "description": "THE move. The target must be a stage of the lead's own board and must be reachable from the current one — a stage graph that forbids the hop is a 400. Landing on a terminal column re-derives the lifecycle from that stage's category, so dragging a card to «Ganado» closes the lead exactly as `PUT /leads/{id}/won` does. Emits `lead.stage_changed` with `changes.stage = { from, to }` and the author, plus the outcome Evento when the status actually flipped. A no-op move (already on that stage) emits nothing. `value_amount` (+ `value_currency`) is the REAL closing price and is acted on only when the target is a won stage: it overwrites the value and stamps `value_amount_close_confirmed_at` before the move, as `PUT /leads/{id}/won` does; for any other target it is ignored.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "suggestion_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  }
                },
                "required": [
                  "stage_id"
                ]
              },
              "example": {
                "stage_id": "55555555-0000-4000-8000-000000000002",
                "reason": "la llamamos y confirmó la hora"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000002",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-22T08:39:25.338Z",
                    "last_activity_at": "2026-09-22T08:39:25.338Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000002",
                      "name": "Contactado",
                      "slug": "contacted",
                      "position": 1,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/pipeline": {
      "put": {
        "summary": "Move a lead to another board",
        "description": "Reclassifies the opportunity onto a different SALES board (400 for a ticket or vehicle board) and lands it on that board's first stage, re-deriving the lifecycle from the landing stage rather than leaving a closed lead parked in an open column. Emits `lead.pipeline_changed`, whose `changes` carries BOTH the board and the stage that moved with it; no separate `lead.stage_changed` fires, so a receiver never sees one board change twice.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "pipeline_id"
                ]
              },
              "example": {
                "pipeline_id": "44444444-0000-4000-8000-000000000002",
                "reason": "es una venta mayorista"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000002",
                    "stage_id": "55555555-0000-4000-8000-000000000003",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000003",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000002"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000002",
                      "kind": "sales",
                      "name": "Ventas mayoristas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/assign": {
      "put": {
        "summary": "Assign a lead to an owner or team",
        "description": "ONE ASSIGNEE OWNS THE WHOLE RELATIONSHIP: reassigning a lead moves every conversation linked to it, and their tickets, to the same person. Send `owner_user_id`, `team_id` or both; an omitted key leaves that side alone, and `null` clears it. A team with no owner draws from the team's rotation. Emits `lead.assigned`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "owner_user_id": "11111111-0000-4000-8000-000000000001",
                "team_id": "cccccccc-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/linea": {
      "put": {
        "summary": "Set a lead's Línea (a person's choice)",
        "description": "Every lead of a clinic carries exactly one Línea (`linea` on the lead: `id`, `nombre`, `source`). It starts from the ad that opened the conversation (`source: ad`), else from the lead's own words matched against the keywords of each Línea (`keyword`), is refined by the booked primera visita's prestación (`prestacion`), is General otherwise (`default`), and staff can correct it here (`staff`). A person's choice is final: nothing automatic changes it afterwards. The Línea must be one of the clinic's live Líneas (`GET /clinic/lineas`). Requires `leads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "linea_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "linea_id"
                ]
              },
              "example": {
                "linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The lead id and its Línea",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "lead_id": "7c2d9a10-0000-4000-8000-000000000011",
                    "linea": {
                      "id": "5b1f0c1e-0000-4000-8000-000000000001",
                      "nombre": "Implantes",
                      "source": "staff"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/won": {
      "put": {
        "summary": "Mark a lead as won",
        "description": "The opportunity closed in the workspace's favour. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. `status`, `closed_at` and `won_lost_reason` are derived from the landing stage, and a `lead.stage_changed` Evento fires alongside the outcome one. `value_amount` here is the REAL closing figure — distinct from any earlier estimate on the lead — and is the only status endpoint that acts on it: it overwrites the value and stamps `value_amount_close_confirmed_at`. Omit it to leave the estimate untouched. Emits `lead.won`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "won",
                      "lost",
                      "unqualified",
                      "open"
                    ]
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  }
                }
              },
              "example": {
                "reason": "cerró con financiamiento propio",
                "value_amount": 8750000,
                "value_currency": "CLP"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "won",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000006",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8750000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": "2026-09-22T08:41:32.967Z",
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": "cerró con financiamiento propio",
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": "2026-09-22T08:41:32.977Z",
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000006",
                      "name": "Ganado",
                      "slug": "won",
                      "position": 6,
                      "won_state": "won",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/lost": {
      "put": {
        "summary": "Mark a lead as lost",
        "description": "The opportunity was real and did not close. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. `status`, `closed_at` and `won_lost_reason` are derived from the landing stage, and a `lead.stage_changed` Evento fires alongside the outcome one. `reason` is the one field worth filling: it is what the win-rate and funnel reads are read against. Emits `lead.lost`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "won",
                      "lost",
                      "unqualified",
                      "open"
                    ]
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  }
                }
              },
              "example": {
                "reason": "compró en otra parte"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "lost",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000007",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": "compró en otra parte",
                    "lost_reason": "bought_elsewhere",
                    "lost_reason_source": "jev",
                    "lost_reason_at": "2026-09-22T08:44:12.501Z",
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": "2026-09-22T08:44:10.331Z",
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000007",
                      "name": "Perdido",
                      "slug": "lost",
                      "position": 7,
                      "won_state": "lost",
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/unqualify": {
      "put": {
        "summary": "Mark a lead as unqualified",
        "description": "There was never an opportunity here — the wrong product, the wrong workspace, a duplicate, a test. A DISTINCT outcome from `lost`, and the distinction is the point: unqualified leads are excluded from win rate on both sides, so qualifying early never looks like losing. The lead MOVES to the lowest-position stage of that category on its board, so the outcome and the column can never disagree — 400 when the board has no stage of that category, which is a board a person has to fix rather than a request to retry. `status`, `closed_at` and `won_lost_reason` are derived from the landing stage, and a `lead.stage_changed` Evento fires alongside the outcome one. Emits `lead.unqualified`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "won",
                      "lost",
                      "unqualified",
                      "open"
                    ]
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "value_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "value_currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 8
                  }
                }
              },
              "example": {
                "reason": "buscaba un servicio que no ofrecemos"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "unqualified",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000003",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": "buscaba un servicio que no ofrecemos",
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": "2026-09-22T08:44:10.331Z",
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000003",
                      "name": "No calificado",
                      "slug": "unqualified",
                      "position": 8,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/reopen": {
      "put": {
        "summary": "Reopen a closed lead",
        "description": "Puts a won, lost or unqualified lead back in play on the first OPEN stage of its board, clearing `closed_at` and `won_lost_reason`. No body. The close is not erased — the timeline and the transition log keep it — so a reopened-and-re-won lead reads as the two events it was.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "99999999-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "display_seq": 89,
                    "title": "María — prueba de manejo Corolla 2022",
                    "notes": null,
                    "status": "open",
                    "source": "conversation",
                    "source_ref": null,
                    "intent": "buy",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": null,
                    "value_amount": 8990000,
                    "value_currency": "CLP",
                    "value_amount_close_confirmed_at": null,
                    "expected_close_at": "2026-10-15T00:00:00.000Z",
                    "score": 72,
                    "score_reason": "pidió hora para probar el auto y confirmó día",
                    "scored_at": "2026-09-21T14:02:11.000Z",
                    "temperature": "warm",
                    "temperature_reason": "respondió pidiendo confirmar la hora y no ha vuelto a escribir",
                    "temperature_at": "2026-09-21T14:02:11.000Z",
                    "won_lost_reason": null,
                    "lost_reason": null,
                    "lost_reason_source": null,
                    "lost_reason_at": null,
                    "payment_method": "financing",
                    "down_payment_amount": 4000000,
                    "budget_max_amount": 11500000,
                    "has_trade_in": false,
                    "trade_in_description": null,
                    "offer_amount": null,
                    "offer_note": null,
                    "financing_term_months": [
                      24,
                      36
                    ],
                    "installment_amount": null,
                    "max_down_payment_amount": null,
                    "net_income_amount": 1900000,
                    "employer": "CGE",
                    "employment_tenure": "15 años",
                    "budget_currency": null,
                    "down_payment_currency": null,
                    "mortgage_preapproval_status": null,
                    "mortgage_bank": null,
                    "mortgage_preapproved_amount": null,
                    "mortgage_preapproved_currency": null,
                    "meets_rent_income_requirement": null,
                    "has_guarantor": null,
                    "deal_terms_meta": {
                      "payment_method": {
                        "source": "ai",
                        "actor_id": null,
                        "actor_label": null,
                        "at": "2026-09-21T14:02:11.000Z"
                      },
                      "down_payment_amount": {
                        "source": "human",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "Camila Rojas",
                        "at": "2026-09-21T15:10:00.000Z"
                      }
                    },
                    "deal_terms_suggestions": {},
                    "closed_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T13:44:02.115Z",
                    "updated_at": "2026-09-21T14:02:11.000Z",
                    "last_activity_at": "2026-09-21T14:02:11.000Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345001"
                    },
                    "stage": {
                      "id": "55555555-0000-4000-8000-000000000001",
                      "name": "Nuevo",
                      "slug": "new",
                      "position": 0,
                      "won_state": null,
                      "pipeline_id": "44444444-0000-4000-8000-000000000001"
                    },
                    "pipeline": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "kind": "sales",
                      "name": "Ventas"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/first-touch": {
      "get": {
        "summary": "One lead's first-touch outcome",
        "description": "Whether the proactive first message for this lead went out, and if not, why: `status` (`none` = never attempted, `pending`, `sent`, `skipped`, `error`), the `skip_reason` (`invalid_phone` carries the number the portal delivered in `skip_evidence.raw_phone`), the classified `error_class`/`error_code` of a failed send (`undeliverable` = WhatsApp could not reach the number), the conversation it opened, and the contact's current phone with `phone_dialable`. `retryable` says whether `POST /leads/{id}/first-touch/retry` would be accepted; `retry_blocked_reason` names why not (`already_sent`, `in_progress`, `do_not_contact`, `source_disabled`, `no_contact`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead first-touch state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "skipped",
                    "skip_reason": "invalid_phone",
                    "skip_evidence": {
                      "raw_phone": "+90773361"
                    },
                    "error_class": null,
                    "error_code": null,
                    "conversation_id": null,
                    "source": "chileautos",
                    "contact_id": "01a14658-50d7-764b-89dd-601c87c24fd7",
                    "phone": "+90773361",
                    "phone_dialable": false,
                    "retryable": true,
                    "retry_blocked_reason": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/first-touch/retry": {
      "post": {
        "summary": "Send the first message again, after fixing the buyer phone",
        "description": "Re-runs the proactive first touch for a lead whose first attempt was skipped or failed — typically because the portal delivered a phone WhatsApp cannot reach. Pass `phone` to correct the contact first (it must pass the dial gate: E.164 or a 9-digit Chilean number, else 400 `invalid_phone`). Uses the configured template and account of the lead's source, reopens the buyer's original inquiry as the context card, and answers with the rail's outcome: `sent` with the new `conversation_id`, or `sent: false` with the `reason` (`burst_skip`, `no_template`, `daily_cap`, …). 409 with the blocking reason when the first touch already went out, is in flight, the person asked not to be contacted, or the source is off.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phone": {
                    "type": "string",
                    "minLength": 6,
                    "maxLength": 32,
                    "description": "Corrected buyer phone, applied to the contact before the retry. E.164 (`+56990773361`) or a 9-digit Chilean national number."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "phone": "+56990773361"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "First-touch retry outcome",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "sent": true,
                    "reason": null,
                    "conversation_id": "c0c0c0c0-0000-4000-8000-000000000459",
                    "phone": "+56990773361"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/interests": {
      "get": {
        "summary": "List what a lead concretely wants",
        "description": "A lead is the opportunity; an INTEREST is the thing being asked for, and there can be several. `resource_type` is `vehicle` · `property` · `product` · `service` · `repair_order` · `custom`, with `resource_id` pointing at the record when it exists in Vitrina and `title` carrying it in words when it does not (\"un SUV gris, 2020 o más nuevo\").",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "LeadInterest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "aaaaaaaa-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": "99999999-0000-4000-8000-000000000001",
                      "resource_type": "vehicle",
                      "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "title": "Toyota Corolla 2022",
                      "quantity": 1,
                      "priority": "high",
                      "notes": "lo quiere gris, con mantenimiento al día",
                      "metadata": {},
                      "created_at": "2026-09-22T08:39:26.884Z",
                      "updated_at": "2026-09-22T08:39:26.884Z",
                      "superseded_at": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add an interest to a lead",
        "description": "Provide `resource_id`, `title`, or both — one of the two is required, because an interest that names neither a record nor a description says nothing. 400 otherwise.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "resource_type": {
                    "type": "string",
                    "enum": [
                      "vehicle",
                      "property",
                      "product",
                      "service",
                      "repair_order",
                      "custom"
                    ]
                  },
                  "resource_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "quantity": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "priority": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "resource_type"
                ]
              },
              "example": {
                "resource_type": "vehicle",
                "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "quantity": 1,
                "priority": "high",
                "notes": "lo quiere gris, con mantenimiento al día"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "LeadInterest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "aaaaaaaa-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": "99999999-0000-4000-8000-000000000001",
                    "resource_type": "vehicle",
                    "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "title": "Toyota Corolla 2022",
                    "quantity": 1,
                    "priority": "high",
                    "notes": "lo quiere gris, con mantenimiento al día",
                    "metadata": {},
                    "created_at": "2026-09-22T08:39:26.884Z",
                    "updated_at": "2026-09-22T08:39:26.884Z",
                    "superseded_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/interests/{interestId}": {
      "delete": {
        "summary": "Remove a lead interest",
        "description": "Deletes the row and answers `{ id, deleted: true }` (200, not 204). Removes it from the record entirely — an interest the customer moved ON from is history worth keeping, so prefer adding the new one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "interestId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "LeadInterest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "aaaaaaaa-0000-4000-8000-000000000001",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a lead interest",
        "description": "Only the fields present in the body are written; at least one is required. Use this when the ask itself changed — a different unit, a different quantity, a different priority.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "interestId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "resource_type": {
                    "type": "string",
                    "enum": [
                      "vehicle",
                      "property",
                      "product",
                      "service",
                      "repair_order",
                      "custom"
                    ]
                  },
                  "resource_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "quantity": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "priority": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20000
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                }
              },
              "example": {
                "priority": "medium"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "LeadInterest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "aaaaaaaa-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": "99999999-0000-4000-8000-000000000001",
                    "resource_type": "vehicle",
                    "resource_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "title": "Toyota Corolla 2022",
                    "quantity": 1,
                    "priority": "medium",
                    "notes": "lo quiere gris, con mantenimiento al día",
                    "metadata": {},
                    "created_at": "2026-09-22T08:39:26.884Z",
                    "updated_at": "2026-09-22T08:39:26.884Z",
                    "superseded_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/conversations": {
      "get": {
        "summary": "List the conversations this opportunity spans",
        "description": "A lead is ONE closable opportunity; a conversation is a temporary session. The customer comes back next week on another channel and it is still the same opportunity, so this relation — not a single `conversation_id` — is the answer to \"which conversations belong to this lead\". `is_origin` marks where the opportunity STARTED (a historical fact) and `is_primary` the thread it is worked on now (editable).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "display_id": "C-122",
                      "channel": "whatsapp",
                      "status": "open",
                      "is_primary": true,
                      "is_origin": true,
                      "linked_at": "2026-09-18T13:44:02.115Z",
                      "linked_by_type": "system",
                      "last_message_date": "2026-09-21T14:01:58.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Link a conversation to a lead",
        "description": "IDEMPOTENT: re-linking an existing pair answers `linked: false` and writes no second timeline entry. `make_primary` moves the primary flag onto this thread, demoting whichever held it. 404 for a lead or a conversation this credential cannot see — BOTH sides are checked, so a member restricted to their own records cannot attach a colleague's thread to a lead they own.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "make_primary": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "conversation_id"
                ]
              },
              "example": {
                "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                "make_primary": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Link result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "linked": true,
                    "is_primary": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/leads/{id}/conversations/{conversationId}": {
      "delete": {
        "summary": "Unlink a conversation from a lead",
        "description": "Detaching the ORIGIN conversation is allowed: the association is editable, but `origin_conversation_id` is a historical fact (\"where this opportunity started\") and stays put, because attribution keeps reading it. Answers `{ unlinked }` — `false` when there was no link to remove.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|L-\\d+)$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "conversationId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Unlink result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "unlinked": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/lead-outreach/status": {
      "get": {
        "summary": "First-touch status for one lead source",
        "description": "Health and recent outcomes of the proactive first message for one lead source (`chileautos`, `yapo`, `mercadolibre`, `website`, …): which rail it uses, whether its account is usable, sends today, and the latest outcomes with the reason a lead was skipped.\n\n`website` is ON by default for every workspace, and a portal (`chileautos`, `mercadolibre`, `yapo`) is ON by default once the workspace has an enabled marketplace account for it. With no block of its own such a source borrows the account and template chain of the first enabled source (`config_origin: inherited`, named in `inherited_from`), or uses the partner WhatsApp account and Vitrina's own templates — for a portal, the workspace's own first-contact templates first, then Vitrina's fallback ones (`platform_default`). An explicit block, `enabled: false` included, always wins. `templates` lists the WhatsApp ladder in send order with each template's state on that account: only `APPROVED` templates are ever sent; a `PENDING` or `REJECTED` one is shown so it is not silently skipped, and `status: null` means it is not on that account. When no template can be sent the first message goes by email, from the workspace mailbox. Each `recent` row carries its lead’s `contact_id` (one row per person on screen) and, on an `error` row, the classified `error_code` (`undeliverable` is Meta 131026; `provider_frequency_capped` and `recipient_experiment` are Meta holding marketing back), never the provider’s message.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Leads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "Lead source key: `chileautos`, `yapo`, `mercadolibre`, `website`, …"
            },
            "required": true,
            "description": "Lead source key: `chileautos`, `yapo`, `mercadolibre`, `website`, …",
            "name": "source",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "First-touch status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "source": "website",
                    "enabled": true,
                    "contact_method": "whatsapp",
                    "template_name": "contacto_sitio_web_nombre",
                    "account": {
                      "id": "c3c3c3c3-0000-4000-8000-000000000001",
                      "name": "WhatsApp",
                      "enabled": true
                    },
                    "account_ok": true,
                    "config_origin": "platform_default",
                    "inherited_from": null,
                    "templates": [
                      {
                        "name": "contacto_sitio_web_nombre",
                        "language": "es",
                        "status": "APPROVED",
                        "category": "MARKETING",
                        "rejection_reason": null,
                        "synced_at": "2026-09-25T13:00:00.000Z"
                      },
                      {
                        "name": "contacto_sitio_web",
                        "language": "es",
                        "status": "PENDING",
                        "category": "UTILITY",
                        "rejection_reason": null,
                        "synced_at": "2026-09-25T13:00:00.000Z"
                      },
                      {
                        "name": "contacto_sitio_web_generico",
                        "language": "es",
                        "status": null,
                        "category": null,
                        "rejection_reason": null,
                        "synced_at": null
                      }
                    ],
                    "sent_today": 3,
                    "daily_cap": 100000,
                    "recent": [
                      {
                        "id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "status": "skipped",
                        "channel": "whatsapp",
                        "skip_reason": "no_phone",
                        "error_class": null,
                        "created_at": "2026-09-25T12:41:00.000Z",
                        "conversation_id": "e5e5e5e5-0000-4000-8000-000000000001",
                        "lead": {
                          "id": "f6f6f6f6-0000-4000-8000-000000000001",
                          "display_id": "L-42",
                          "title": "Consulta desde el sitio web",
                          "conversation_id": "e5e5e5e5-0000-4000-8000-000000000001"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/search": {
      "get": {
        "summary": "Search contacts (paged; meta.total is the match count, not the page size)",
        "description": "The directory read. `q` matches across name, email, phone and `external_id` at once, case- and accent-insensitively («Jose» finds «José»).\n\n**`meta.total` is the number of MATCHES, not the page size.** It used to be the latter, which made it a restatement of `limit` — a client that asked for 1.000 was told «total: 1.000» and drew a book that stopped there.\n\n`lifecycle_stage` accepts one stage or a comma-separated list — `lifecycle_stage=unknown,prospect,qualified_prospect` is how the app asks for its «Prospectos» bucket. Every token is validated against `unknown|prospect|qualified_prospect|customer|repeat_customer|inactive|blocked`; an unrecognised one is a 400 rather than a filter that matches nobody.\n\nRows carry two badge arrays the stored contact does not: `channels` (the distinct `contact_channel` kinds) and `lead_sources` (the distinct `lead.source` values), plus `tags` — every tag on the contact as `{ id, name, display_name }` (`name` is the slug, `display_name` the label), oldest attachment first. **`lead_sources` outranks `origin_channel`** — provenance derived from real leads beats the one an operator typed, so `origin_channel` is only the answer when `lead_sources` is empty.\n\nThe filters combine and every one of them is optional: `lifecycle_stage`, `channel` (a linked channel of that kind), `lead_source`, `tag_id`, `company_id` (a uuid, or the literal `none` for the contacts that belong to no company), `instagram` and `exclude_id`.\n\n`instagram` is an EXACT match on the contact's Instagram handle (`social.instagram`), case-insensitive and with the leading `@` optional on either side: `instagram=@BlackIto72` and `instagram=blackito72` both find a contact stored as `@blackito72` or `blackito72`. `q` does not search handles, so this is how to find an Instagram contact, which usually has no phone.\n\nMerged contacts are excluded unless `include_merged=true`. `display_name` is never empty and `named` says whether it IS the person’s name: when a contact has none, `display_name` falls back to a formatted phone, an email or a channel handle and `named` is false. Check `named` before putting `display_name` into text a customer reads — otherwise a template greets somebody by their own phone number.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "exclude_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "include_merged",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 25
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": false,
            "name": "lifecycle_stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40
            },
            "required": false,
            "name": "channel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 60
            },
            "required": false,
            "name": "lead_source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "tag_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ]
            },
            "required": false,
            "name": "company_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "required": false,
            "name": "instagram",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of contacts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ContactSearchRow"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "total",
                        "limit",
                        "offset"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "external_id": null,
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345678",
                      "language": "es",
                      "country": "CL",
                      "brand": null,
                      "lifecycle_stage": "prospect",
                      "job_title": "Gerente de operaciones",
                      "birthday": "1989-03-15",
                      "birthday_year_known": true,
                      "vehicle_brand": "Chevrolet",
                      "vehicle_model": "Aveo",
                      "vehicle_year": 2015,
                      "city": "Providencia",
                      "company_id": "15151515-0000-4000-8000-000000000001",
                      "social": {
                        "instagram": "@mariagonzalez"
                      },
                      "email_consent": false,
                      "email_status": "subscribed",
                      "avatar_url": null,
                      "social_stats": {},
                      "origin_channel": "manual",
                      "imported_from": null,
                      "merged_into_contact_id": null,
                      "merged_at": null,
                      "blocked_at": null,
                      "archived_at": null,
                      "spam_at": null,
                      "bot_replies_disabled_at": null,
                      "bot_replies_disabled_by": null,
                      "created_at": "2026-09-21T14:03:11.000Z",
                      "updated_at": "2026-09-21T14:03:11.000Z",
                      "tax_id": null,
                      "tax_id_kind": null,
                      "person_kind": null,
                      "legal_name": null,
                      "giro": null,
                      "representative_contact_id": null,
                      "address_street": null,
                      "address_number": null,
                      "address_unit": null,
                      "comuna_code": null,
                      "region_code": null,
                      "address_source": null,
                      "first_touch": null,
                      "display_name": "María González",
                      "named": true,
                      "channels": [
                        "whatsapp"
                      ],
                      "lead_sources": [
                        "website"
                      ],
                      "tags": [
                        {
                          "id": "0192a0c4-7b1e-7c3a-9d2f-4e5a6b7c8d9e",
                          "name": "campana-oct26-a",
                          "display_name": "Campaña Oct26 A"
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "total": 148,
                    "limit": 25,
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/stats": {
      "get": {
        "summary": "Contact directory facet counts (live contacts only)",
        "description": "The denominators behind the directory’s filter chips, computed over the whole book rather than the loaded page: totals by lifecycle stage, by channel, by lead source, plus how many contacts are reachable by email and by phone and how many duplicate clusters exist.\n\nMerged and archived contacts are excluded — this counts the live directory, which is what every number a client renders next to a filter has to agree with.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "responses": {
          "200": {
            "description": "Directory facet counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 148,
                    "by_lifecycle": {
                      "unknown": 96,
                      "prospect": 31,
                      "customer": 21
                    },
                    "by_channel": {
                      "whatsapp": 88,
                      "instagram": 12,
                      "email": 7
                    },
                    "by_lead_source": {
                      "website": 44,
                      "conversation": 31,
                      "import": 12,
                      "manual": 9
                    },
                    "email_reachable": 61,
                    "phone_reachable": 132,
                    "duplicate_candidates": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/duplicates": {
      "get": {
        "summary": "Duplicate contact clusters (live contacts only)",
        "description": "Groups of live contacts sharing a phone (last ten digits, minimum eight) or a lowercased email, over the whole book rather than one page. A phone group and an email group covering the same people are reported once, as the phone one. Ordered biggest first and capped at 100 clusters; `meta.total` is the real cluster count, so a client can tell «these are all of them» from «these are the worst».\n\nEach cluster carries its members in full, so a merge screen needs no second read. Feed the survivor and the rest to `POST /contacts/{id}/merge`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "responses": {
          "200": {
            "description": "Duplicate clusters, biggest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "phone:56912345678",
                      "reason": "phone",
                      "value": "56912345678",
                      "contacts": [
                        {
                          "id": "22222222-0000-4000-8000-000000000001",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "external_id": null,
                          "name": "María González",
                          "email": "maria.gonzalez@example.cl",
                          "phone": "+56912345678",
                          "language": "es",
                          "country": "CL",
                          "brand": null,
                          "lifecycle_stage": "prospect",
                          "job_title": "Gerente de operaciones",
                          "birthday": "1989-03-15",
                          "birthday_year_known": true,
                          "vehicle_brand": "Chevrolet",
                          "vehicle_model": "Aveo",
                          "vehicle_year": 2015,
                          "city": "Providencia",
                          "company_id": "15151515-0000-4000-8000-000000000001",
                          "social": {
                            "instagram": "@mariagonzalez"
                          },
                          "email_consent": false,
                          "email_status": "subscribed",
                          "avatar_url": null,
                          "social_stats": {},
                          "origin_channel": "manual",
                          "imported_from": null,
                          "merged_into_contact_id": null,
                          "merged_at": null,
                          "blocked_at": null,
                          "archived_at": null,
                          "spam_at": null,
                          "bot_replies_disabled_at": null,
                          "bot_replies_disabled_by": null,
                          "created_at": "2026-09-21T14:03:11.000Z",
                          "updated_at": "2026-09-21T14:03:11.000Z",
                          "tax_id": null,
                          "tax_id_kind": null,
                          "person_kind": null,
                          "legal_name": null,
                          "giro": null,
                          "representative_contact_id": null,
                          "address_street": null,
                          "address_number": null,
                          "address_unit": null,
                          "comuna_code": null,
                          "region_code": null,
                          "address_source": null,
                          "first_touch": null,
                          "display_name": "María González",
                          "named": true,
                          "channels": [
                            "whatsapp"
                          ],
                          "lead_sources": [
                            "website"
                          ]
                        },
                        {
                          "id": "22222222-0000-4000-8000-000000000002",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "external_id": null,
                          "name": "María González",
                          "email": null,
                          "phone": "+56912345678",
                          "language": "es",
                          "country": "CL",
                          "brand": null,
                          "lifecycle_stage": "prospect",
                          "job_title": "Gerente de operaciones",
                          "birthday": "1989-03-15",
                          "birthday_year_known": true,
                          "vehicle_brand": "Chevrolet",
                          "vehicle_model": "Aveo",
                          "vehicle_year": 2015,
                          "city": "Providencia",
                          "company_id": null,
                          "social": {
                            "instagram": "@mariagonzalez"
                          },
                          "email_consent": false,
                          "email_status": "subscribed",
                          "avatar_url": null,
                          "social_stats": {},
                          "origin_channel": "manual",
                          "imported_from": null,
                          "merged_into_contact_id": null,
                          "merged_at": null,
                          "blocked_at": null,
                          "archived_at": null,
                          "spam_at": null,
                          "bot_replies_disabled_at": null,
                          "bot_replies_disabled_by": null,
                          "created_at": "2026-09-21T14:03:11.000Z",
                          "updated_at": "2026-09-21T14:03:11.000Z",
                          "tax_id": null,
                          "tax_id_kind": null,
                          "person_kind": null,
                          "legal_name": null,
                          "giro": null,
                          "representative_contact_id": null,
                          "address_street": null,
                          "address_number": null,
                          "address_unit": null,
                          "comuna_code": null,
                          "region_code": null,
                          "address_source": null,
                          "first_touch": null,
                          "display_name": "María González",
                          "named": true,
                          "channels": [],
                          "lead_sources": []
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts": {
      "post": {
        "summary": "Create a contact",
        "description": "Only `name` is required — a contact with neither email nor phone is creatable, and is unreachable on every channel until one is added.\n\nThis creates a row directly and does NOT deduplicate: an existing contact with the same email or phone is not matched or merged, you simply get a second contact. Use `GET /contacts/search` first, or `POST /contacts/import`, which does match on identity.\n\n`lifecycle_stage` defaults to `unknown`. A new contact is **not** marketable: `email_consent` starts false and is never granted implicitly by creation.\n\n`origin_channel` is the operator-declared «Canal de origen», and it is a FALLBACK for provenance rather than the truth: the contacts list derives Origen from the distinct `lead.source` values of the contact’s leads and only falls back to this when there are none. Its vocabulary is `lead.source`’s, value for value, so both render as one badge and answer one filter.\n\nEmits `contact.created` — as does every other way a contact comes into being (an inbound message, an import row, a portal lead, a form, a call), exactly once each.\n\nAnswers **201 with `{ data }` and no `meta`** — the created contact, nothing else. `display_name` is never empty and `named` says whether it IS the person’s name: when a contact has none, `display_name` falls back to a formatted phone, an email or a channel handle and `named` is false. Check `named` before putting `display_name` into text a customer reads — otherwise a template greets somebody by their own phone number.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "first_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "last_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "format": "email"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "language": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 10
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 10
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "lifecycle_stage": {
                    "type": "string",
                    "enum": [
                      "unknown",
                      "prospect",
                      "qualified_prospect",
                      "customer",
                      "repeat_customer",
                      "inactive",
                      "blocked"
                    ]
                  },
                  "job_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "birthday": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^(\\d{4}-\\d{2}-\\d{2}|--\\d{2}-\\d{2})$"
                  },
                  "vehicle_brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "vehicle_model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "vehicle_year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "company_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "social": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "origin_channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "conversation",
                      "marketplace",
                      "manual",
                      "import",
                      "ai_agent",
                      "chileautos",
                      "yapo",
                      "mercadolibre",
                      "website",
                      "facebook_marketplace",
                      "form",
                      "campaign"
                    ]
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "tax_id_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "person_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "natural",
                      "juridica"
                    ]
                  },
                  "legal_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "giro": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "representative_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "address_street": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  }
                }
              },
              "example": {
                "name": "María González",
                "email": "maria.gonzalez@example.cl",
                "phone": "+56912345678",
                "job_title": "Gerente de operaciones",
                "lifecycle_stage": "prospect",
                "origin_channel": "manual",
                "company_id": "15151515-0000-4000-8000-000000000001",
                "birthday": "1989-03-15",
                "vehicle_brand": "Chevrolet",
                "vehicle_model": "Aveo",
                "vehicle_year": 2015
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "prospect",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-21T14:03:11.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/contacts/{id}": {
      "get": {
        "summary": "Get a contact",
        "description": "One contact by uuid, with the same `channels` / `lead_sources` badge arrays a search row carries — so a deep link and a list row render identically.\n\nA MERGED contact still answers here rather than 404ing, carrying `merged_into_contact_id` and `merged_at`. That is deliberate: an integration holding an id that was merged away can follow the pointer to the survivor instead of losing the person. `display_name` is never empty and `named` says whether it IS the person’s name: when a contact has none, `display_name` falls back to a formatted phone, an email or a channel handle and `named` is false. Check `named` before putting `display_name` into text a customer reads — otherwise a template greets somebody by their own phone number.\n\n`first_touch` is the first ad click a public booking surface (the hosted booking page, the clinic widget) saw for this person: `utm_*`, `fbclid` / `gclid`, the tag's `anonymous_id`, `surface`, `captured_at`. Written once and never overwritten; `null` for most contacts.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "prospect",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-21T14:03:11.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true,
                    "channels": [
                      "whatsapp"
                    ],
                    "lead_sources": [
                      "website"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Update a contact",
        "description": "Partial update — send only the fields you are changing; at least one is required. `null` clears a nullable field (including `origin_channel`, editable after the fact so an operator who learns the walk-in was a referral can correct it).\n\nA name written here is an OPERATOR’s word and outranks every automatic source afterwards: a later channel profile or address-book sync will not overwrite it. Clearing the name clears that provenance too, so automatic sources may fill it again.\n\nA merged contact is read-only; patching one answers 404.\n\nLegal identity is validated properly, not just accepted: `tax_id` is checked digit and all, `tax_id` and `tax_id_kind` always travel together, and a persona jurídica must be complete. A duplicate `tax_id` in the workspace is a 409 — one legal identity, one counterparty — with `code: UNIQUE_CONFLICT`, `details: { reason: \"tax_id_taken\", field: \"tax_id\", conflicting_contact_id, conflicting_contact_name }` and `field_errors.tax_id` («Este RUT ya pertenece a …»), so a client can open the contact that holds it. `POST /contacts` refuses the same way. Every other legal-identity or address refusal also carries `field_errors` keyed by the column to fix.\n\nEmits `contact.updated` with the field names that were written. The legal identity (`tax_id`, `tax_id_kind`, `person_kind`, `legal_name`, `giro`, `representative_contact_id`) and the street address (`address_street`, `address_number`, `address_unit`, `comuna_code`) are withheld from a credential that does not hold `contacts:read` — including on this write’s own response, because otherwise a `contacts:write`-only key could read a RUT by writing an unrelated field. Everything else, `region_code` included, is returned unchanged.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email_consent": {
                    "type": "boolean"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "first_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "last_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 254,
                    "format": "email"
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 50
                  },
                  "language": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 10
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 10
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "lifecycle_stage": {
                    "type": "string",
                    "enum": [
                      "unknown",
                      "prospect",
                      "qualified_prospect",
                      "customer",
                      "repeat_customer",
                      "inactive",
                      "blocked"
                    ]
                  },
                  "job_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "birthday": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^(\\d{4}-\\d{2}-\\d{2}|--\\d{2}-\\d{2})$"
                  },
                  "vehicle_brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "vehicle_model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "vehicle_year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1900,
                    "maximum": 2100
                  },
                  "city": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "company_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "social": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {
                      "type": "string",
                      "maxLength": 500
                    }
                  },
                  "origin_channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "conversation",
                      "marketplace",
                      "manual",
                      "import",
                      "ai_agent",
                      "chileautos",
                      "yapo",
                      "mercadolibre",
                      "website",
                      "facebook_marketplace",
                      "form",
                      "campaign"
                    ]
                  },
                  "tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "tax_id_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "person_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "natural",
                      "juridica"
                    ]
                  },
                  "legal_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "giro": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "representative_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "address_street": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  }
                }
              },
              "example": {
                "lifecycle_stage": "customer",
                "job_title": "Directora de operaciones"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "customer",
                    "job_title": "Directora de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-22T11:20:45.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/duplicates": {
      "get": {
        "summary": "Suggest duplicate contacts for one contact",
        "description": "Merge candidates for ONE named contact — live contacts matching its phone (last eight digits, so `+56 9 1234 5678` and `912345678` meet), its email, or its exact name. A different question from `GET /contacts/duplicates`, which sweeps the whole book.\n\n`meta.primary_id` echoes the contact you asked about, so a client rendering several of these at once cannot mix them up. A contact with no name, phone or email returns an empty list rather than matching everything.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Merge candidates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "22222222-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "external_id": null,
                      "name": "María González",
                      "email": null,
                      "phone": "+56912345678",
                      "language": "es",
                      "country": "CL",
                      "brand": null,
                      "lifecycle_stage": "prospect",
                      "job_title": "Gerente de operaciones",
                      "birthday": "1989-03-15",
                      "birthday_year_known": true,
                      "vehicle_brand": "Chevrolet",
                      "vehicle_model": "Aveo",
                      "vehicle_year": 2015,
                      "city": "Providencia",
                      "company_id": "15151515-0000-4000-8000-000000000001",
                      "social": {
                        "instagram": "@mariagonzalez"
                      },
                      "email_consent": false,
                      "email_status": "subscribed",
                      "avatar_url": null,
                      "social_stats": {},
                      "origin_channel": "manual",
                      "imported_from": null,
                      "merged_into_contact_id": null,
                      "merged_at": null,
                      "blocked_at": null,
                      "archived_at": null,
                      "spam_at": null,
                      "bot_replies_disabled_at": null,
                      "bot_replies_disabled_by": null,
                      "created_at": "2026-09-21T14:03:11.000Z",
                      "updated_at": "2026-09-21T14:03:11.000Z",
                      "tax_id": null,
                      "tax_id_kind": null,
                      "person_kind": null,
                      "legal_name": null,
                      "giro": null,
                      "representative_contact_id": null,
                      "address_street": null,
                      "address_number": null,
                      "address_unit": null,
                      "comuna_code": null,
                      "region_code": null,
                      "address_source": null,
                      "first_touch": null,
                      "display_name": "María González",
                      "named": true
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "primary_id": "22222222-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/merge": {
      "post": {
        "summary": "Merge other contacts into this one",
        "description": "⚠ **Irreversible. There is no unmerge, here or in the app.** The contact in the path SURVIVES; every id in `secondary_ids` becomes a tombstone.\n\nWhat moves onto the survivor: conversations, channel identities, leads, tickets, notas de venta, credit applications, and any contact that named a secondary as its legal representative. What is FILLED, only where the survivor was empty: name, email, phone, language, country, brand, avatar, the per-channel `social` map, and the `tax_id` + `tax_id_kind` pair. A value the survivor already had is never overwritten. `filled_fields` names the columns that gained something — names, not values.\n\nThe tombstones keep answering `GET /contacts/{id}` with `merged_into_contact_id` set, so stored ids can be repointed.\n\nRefused with a 400 that says why: an id from another workspace, an already-merged secondary, a survivor that is itself a tombstone, or a merge that would make the survivor its own legal representative (a persona jurídica and the person who signs for it are not the same party).\n\nEmits `contact.merged`, whose notice carries the survivor as the resource and the tombstoned ids in `changes.contact_id.from`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "secondary_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 50
                  }
                },
                "required": [
                  "secondary_ids"
                ]
              },
              "example": {
                "secondary_ids": [
                  "22222222-0000-4000-8000-000000000002"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The survivor plus what the merge moved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "primary": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "external_id": null,
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56912345678",
                      "language": "es",
                      "country": "CL",
                      "brand": null,
                      "lifecycle_stage": "prospect",
                      "job_title": "Gerente de operaciones",
                      "birthday": "1989-03-15",
                      "birthday_year_known": true,
                      "vehicle_brand": "Chevrolet",
                      "vehicle_model": "Aveo",
                      "vehicle_year": 2015,
                      "city": "Providencia",
                      "company_id": "15151515-0000-4000-8000-000000000001",
                      "social": {
                        "instagram": "@mariagonzalez"
                      },
                      "email_consent": false,
                      "email_status": "subscribed",
                      "avatar_url": null,
                      "social_stats": {},
                      "origin_channel": "manual",
                      "imported_from": null,
                      "merged_into_contact_id": null,
                      "merged_at": null,
                      "blocked_at": null,
                      "archived_at": null,
                      "spam_at": null,
                      "bot_replies_disabled_at": null,
                      "bot_replies_disabled_by": null,
                      "created_at": "2026-09-21T14:03:11.000Z",
                      "updated_at": "2026-09-21T14:03:11.000Z",
                      "tax_id": null,
                      "tax_id_kind": null,
                      "person_kind": null,
                      "legal_name": null,
                      "giro": null,
                      "representative_contact_id": null,
                      "address_street": null,
                      "address_number": null,
                      "address_unit": null,
                      "comuna_code": null,
                      "region_code": null,
                      "address_source": null,
                      "first_touch": null,
                      "display_name": "María González",
                      "named": true
                    },
                    "merged_count": 1,
                    "conversations_reassigned": 3,
                    "filled_fields": [
                      "email"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/contacts/{id}/block": {
      "post": {
        "summary": "Block or unblock a contact",
        "description": "`{ \"value\": true }` stamps `blocked_at` and moves the contact to `lifecycle_stage: \"blocked\"`; `false` clears the stamp and resets the stage to `unknown` — **the previous commercial stage is not recoverable**, so read it first if it matters.\n\nBlocking is about the person, not about one thread: their next conversation is born filtered. To silence the AI for a contact while keeping the conversation in the inbox, use `POST /contacts/{id}/bot-replies-disabled` instead.\n\nEmits `contact.updated`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact after the toggle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "blocked",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": "2026-09-22T11:24:02.000Z",
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-22T11:24:02.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/archive": {
      "post": {
        "summary": "Archive or unarchive a contact",
        "description": "Directory hygiene, and the closest thing to a delete: an archived contact disappears from the directory and its facet counts while everything that references it — conversations, leads, notas de venta — stays intact. Unlike block and report-spam it does NOT touch `lifecycle_stage` and does not filter future conversations.\n\n`{ \"value\": false }` restores it. There is no hard delete for a contact in this API.\n\nEmits `contact.updated`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact after the toggle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "prospect",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": "2026-09-22T11:25:10.000Z",
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-22T11:25:10.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/report-spam": {
      "post": {
        "summary": "Flag or unflag a contact as spam",
        "description": "Same consequences as block — `spam_at` is stamped and `lifecycle_stage` becomes `blocked`, reset to `unknown` when cleared — with a different reason recorded, which is what a workspace reviewing its filtered traffic later needs to tell the two apart.\n\nEmits `contact.updated`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact after the toggle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "blocked",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": "2026-09-22T11:24:02.000Z",
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": null,
                    "bot_replies_disabled_by": null,
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-22T11:24:02.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/bot-replies-disabled": {
      "post": {
        "summary": "Turn the AI off (or back on) for one contact, on every channel",
        "description": "Suppresses the AUTONOMOUS reply only. Inbound messages are still ingested and the conversation is still visible and assignable — the thread is answered by a person. Unlike block/report-spam this does NOT touch the contact’s `lifecycle_stage` and does NOT quarantine future conversations.\n\n`bot_replies_disabled_by` records the caller, so a client can render «turned off by Camila on 3 September» without a second read. Clearing the flag clears both — a stale «who» next to an empty «when» is worse than nothing.\n\nA single conversation can opt back into the AI with `POST /conversations/{id}/bot-gate-override`.\n\nEmits `contact.updated`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact after the toggle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "prospect",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": "2026-09-22T11:26:40.000Z",
                    "bot_replies_disabled_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-09-22T11:26:40.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/team-account": {
      "post": {
        "summary": "Mark a contact as the workspace’s own team (or not)",
        "description": "For a team member’s PERSONAL account writing in — typically their own Instagram replying to the business’s story. A team contact is not a customer: the AI does not answer it, no automation fires on its messages, and the Ads reports do not count it as a person (its future outcomes are recorded locally and never sent to the ad platforms). Its conversations stay in the inbox.\n\n`value: true` sets `team_account_at` (+ `team_account_user_id` when `user_id` names a member of this workspace, and `team_account_by` = the caller) and turns the AI off for the contact if it was on (`bot_replies_disabled_at`). `value: false` clears the three `team_account_*` fields only — the AI stays off until it is turned back on with `POST /contacts/{id}/bot-replies-disabled`.\n\nNothing is deleted; past messages stay as they are. A contact is also marked automatically when its Instagram username is one a member saved in `instagram_handles` (`PUT /memberships/{id}`).\n\nEmits `contact.updated`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  },
                  "user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": true,
                "user_id": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contact after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "22222222-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "external_id": null,
                    "name": "María González",
                    "email": "maria.gonzalez@example.cl",
                    "phone": "+56912345678",
                    "language": "es",
                    "country": "CL",
                    "brand": null,
                    "lifecycle_stage": "prospect",
                    "job_title": "Gerente de operaciones",
                    "birthday": "1989-03-15",
                    "birthday_year_known": true,
                    "vehicle_brand": "Chevrolet",
                    "vehicle_model": "Aveo",
                    "vehicle_year": 2015,
                    "city": "Providencia",
                    "company_id": "15151515-0000-4000-8000-000000000001",
                    "social": {
                      "instagram": "@mariagonzalez"
                    },
                    "email_consent": false,
                    "email_status": "subscribed",
                    "avatar_url": null,
                    "social_stats": {},
                    "origin_channel": "manual",
                    "imported_from": null,
                    "merged_into_contact_id": null,
                    "merged_at": null,
                    "blocked_at": null,
                    "archived_at": null,
                    "spam_at": null,
                    "bot_replies_disabled_at": "2026-10-06T14:02:11.000Z",
                    "bot_replies_disabled_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-21T14:03:11.000Z",
                    "updated_at": "2026-10-06T14:02:11.000Z",
                    "tax_id": null,
                    "tax_id_kind": null,
                    "person_kind": null,
                    "legal_name": null,
                    "giro": null,
                    "representative_contact_id": null,
                    "address_street": null,
                    "address_number": null,
                    "address_unit": null,
                    "comuna_code": null,
                    "region_code": null,
                    "address_source": null,
                    "first_touch": null,
                    "display_name": "María González",
                    "named": true,
                    "team_account_at": "2026-10-06T14:02:11.000Z",
                    "team_account_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_account_by": "11111111-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/import": {
      "post": {
        "summary": "Import contacts (dry_run returns the dedupe plan without writing)",
        "description": "Send **exactly one** of `csv` (raw text, RFC 4180, with an optional `mapping` from header to field) or `rows` (already mapped, up to 20.000 per call). Sending both, or neither, is a 400. A row may carry a `tags` cell (`;`- or `,`-separated) and `custom:<key>` cells.\n\n**This is the deduplicating way in, and it plans before it writes.** Every identifier is normalized first — the phone to E.164 (a Chilean `912345678` is `+56912345678`), the email lowercased, the external id trimmed. Rows of the call that share a normalized phone, email or external id — or that resolve to the same existing contact — are ONE group: the first row's values win and later rows only fill blanks (`file_conflicts` lists what they disagreed on). Each group is then matched against the workspace on the same normalized keys, including the contact's channel identities (its WhatsApp number can live there with `phone` empty): no match → `create`, one → `merge` (with the `fills` it would write and the `conflicts` it would keep or overwrite), several → `ambiguous`. A row with no usable identifier is `invalid`; an empty one is `skip`; a non-leader row of a group is `duplicate_in_file`.\n\n`dry_run: true` returns that plan (`groups[]` + `rows[]` + `summary`) and writes nothing. To commit, send the rows again with `decisions` — one per group: `merge` (into `contact_id`, optionally folding the other matches in first with `merge_contact_ids`), `create` or `skip`, plus `detach` for member rows that are someone else. Each decision echoes the group's `rows` and `expected_contact_ids`; a group whose rows or matches changed since the review is refused as `stale_plan` and writes nothing. Without `decisions` every group takes its plan default and an ambiguous group is skipped — a target is never guessed. Dedupe only sees the rows of ONE call: plan a file in one dry run, and commit it in chunks that each hold whole groups, with `row_numbers` carrying the file's own row numbers.\n\n`conflict_policy` (`keep_existing` by default) decides whether a merge overwrites a value the contact already holds; phone, email and external id are never overwritten. `tags` are applied to every created and merged contact (missing tags are created).\n\n`email_consent_attested: true` is the ONLY way an import turns `email_consent` on, and it is an assertion the caller makes about every row in the file: that these people opted in. The response then carries `consent_gained` and `marketable_after` — it changes the bill. A contact is **marketable** when it has a valid email address, `email_consent` is true, `email_status` is `subscribed` (not unsubscribed, bounced or complained), and it is not merged, archived or blocked. All four conditions, not just consent.\n\nThe importer deliberately does not fire per-contact automations, but it DOES emit one `contact.created` per contact it created and one `contact.updated` per contact it merged into.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "csv": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 10000000
                  },
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "email": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "phone": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "external_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "country": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "language": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "city": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "job_title": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "brand": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "lifecycle_stage": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "birthday": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "vehicle_brand": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "vehicle_model": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "vehicle_year": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        },
                        "tags": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 1000
                        }
                      },
                      "additionalProperties": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 1000
                      }
                    },
                    "maxItems": 20000
                  },
                  "row_numbers": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "exclusiveMinimum": 0
                    },
                    "maxItems": 20000
                  },
                  "mapping": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  },
                  "dry_run": {
                    "type": "boolean"
                  },
                  "email_consent_attested": {
                    "type": "boolean"
                  },
                  "conflict_policy": {
                    "type": "string",
                    "enum": [
                      "keep_existing",
                      "overwrite"
                    ]
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 60
                    },
                    "maxItems": 20
                  },
                  "decisions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "row": {
                          "type": "integer",
                          "exclusiveMinimum": 0
                        },
                        "rows": {
                          "type": "array",
                          "items": {
                            "type": "integer",
                            "exclusiveMinimum": 0
                          },
                          "minItems": 1,
                          "maxItems": 20000
                        },
                        "expected_contact_ids": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "maxItems": 100
                        },
                        "action": {
                          "type": "string",
                          "enum": [
                            "merge",
                            "create",
                            "skip"
                          ]
                        },
                        "contact_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "merge_contact_ids": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "maxItems": 100
                        },
                        "detach": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "row": {
                                "type": "integer",
                                "exclusiveMinimum": 0
                              },
                              "action": {
                                "type": "string",
                                "enum": [
                                  "create",
                                  "skip"
                                ]
                              }
                            },
                            "required": [
                              "row",
                              "action"
                            ]
                          },
                          "maxItems": 20000
                        }
                      },
                      "required": [
                        "row",
                        "rows",
                        "expected_contact_ids",
                        "action"
                      ]
                    },
                    "maxItems": 20000
                  }
                }
              },
              "example": {
                "rows": [
                  {
                    "name": "Pedro Ramírez",
                    "phone": "987654321",
                    "tags": "campaña-octubre"
                  },
                  {
                    "name": "Pedro Ramírez",
                    "phone": "+56 9 8765 4321",
                    "email": "pedro@example.cl"
                  }
                ],
                "dry_run": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The per-row import report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "dry_run": true,
                    "total": 2,
                    "created": 0,
                    "merged": 1,
                    "skipped": 0,
                    "failed": 0,
                    "summary": {
                      "create": 0,
                      "merge": 1,
                      "duplicate_in_file": 1,
                      "ambiguous": 0,
                      "invalid": 0,
                      "skip": 0
                    },
                    "conflict_policy": "keep_existing",
                    "groups": [
                      {
                        "row": 1,
                        "rows": [
                          1,
                          2
                        ],
                        "outcome": "merge",
                        "name": "Pedro Ramírez",
                        "phone": "+56987654321",
                        "email": "pedro@example.cl",
                        "external_id": null,
                        "candidates": [
                          {
                            "id": "22222222-0000-4000-8000-000000000001",
                            "name": "Pedro Ramírez",
                            "phone": "+56987654321",
                            "email": null,
                            "external_id": null,
                            "archived": false,
                            "matched_on": [
                              "phone"
                            ]
                          }
                        ],
                        "contact_id": "22222222-0000-4000-8000-000000000001",
                        "fills": [
                          {
                            "field": "email",
                            "value": "pedro@example.cl"
                          }
                        ],
                        "conflicts": [],
                        "file_conflicts": [],
                        "tags": [
                          "campaña-octubre"
                        ],
                        "warnings": []
                      }
                    ],
                    "rows": [
                      {
                        "row": 1,
                        "outcome": "merge",
                        "group": 1,
                        "name": "Pedro Ramírez"
                      },
                      {
                        "row": 2,
                        "outcome": "duplicate_in_file",
                        "group": 1,
                        "name": "Pedro Ramírez"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/contacts/export": {
      "get": {
        "summary": "Export contacts as a CSV file",
        "description": "Streams the whole directory as UTF-8 CSV (with a BOM, so spreadsheets open the accents correctly), narrowed by the same `q` and `lifecycle_stage` the search takes. It is paged internally by keyset, so a large book is neither buffered nor truncated — there is no `limit` to get wrong.\n\nThe columns are the importable ones, so an export can be edited and fed straight back to `POST /contacts/import`. The legal identity and the structured address are NOT among them.\n\nAnswers `text/csv` with a `Content-Disposition` filename, not JSON.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "unknown",
                "prospect",
                "qualified_prospect",
                "customer",
                "repeat_customer",
                "inactive",
                "blocked"
              ]
            },
            "required": false,
            "name": "lifecycle_stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV file",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/bulk-consent": {
      "post": {
        "summary": "Record email-marketing consent for many contacts",
        "description": "Flips `email_consent` to true for up to 1.000 contacts and answers `{ flipped, marketable }` — how many actually changed, and the workspace’s new marketable total.\n\n**`flipped` is normally lower than the number of ids sent**, and that is not an error: contacts that already consented, and contacts with no email address, are skipped. Do not treat a shortfall as a partial failure.\n\nThis endpoint RECORDS an assertion; it does not obtain consent. Nothing here captures when, how or on what wording the contact opted in — unlike the outreach-consent path, which stamps a server-side timestamp precisely because it has to serve as evidence. The operator calling this is attesting that consent exists and remains responsible for proving it.\n\n**It is one-way and it changes the bill.** There is no bulk revoke here (a single contact is revoked through `POST /contacts/{id}/outbound-preferences`), and the marketable count this raises is the billed unit of the email rate card. A contact is **marketable** when it has a valid email address, `email_consent` is true, `email_status` is `subscribed` (not unsubscribed, bounced or complained), and it is not merged, archived or blocked. All four conditions, not just consent.\n\nRegistered before `/{id}` so the literal path is not read as an id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 1000
                  }
                },
                "required": [
                  "contact_ids"
                ]
              },
              "example": {
                "contact_ids": [
                  "22222222-0000-4000-8000-000000000001",
                  "22222222-0000-4000-8000-000000000002"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ flipped, marketable }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "flipped": 1,
                    "marketable": 62
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/contacts/marketable-count": {
      "get": {
        "summary": "How many contacts email marketing may reach",
        "description": "Answers `{ marketable }`. A contact is **marketable** when it has a valid email address, `email_consent` is true, `email_status` is `subscribed` (not unsubscribed, bounced or complained), and it is not merged, archived or blocked. All four conditions, not just consent.\n\n**This is the billed figure** for the email rate card — a live snapshot of the contact base, not a windowed count, so it moves as contacts consent, unsubscribe or bounce. Show it before a consent import if the customer should see the cost implication.\n\nZero is the expected answer for a workspace that has never captured consent: the flag defaults to false and was deliberately never backfilled, so an established workspace with thousands of contacts can legitimately report 0.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "responses": {
          "200": {
            "description": "`{ marketable }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "marketable": 62
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/tags/bulk": {
      "post": {
        "summary": "Tag many contacts at once",
        "description": "Applies one tag to up to 500 contacts and answers `{ tagged, tag_id }`. Send **either** `tag.tag_id` for an existing tag **or** `tag.name` to resolve-or-create by slug — exactly one, not both.\n\n`tagged` counts the attachments actually made, so contacts that already carried the tag are not counted again. Ids the workspace does not own contribute nothing and do not fail the call, so this is not a way to check which ids are valid.\n\nRegistered before `/{id}` so `tags` is never read as a contact id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  },
                  "tag": {
                    "type": "object",
                    "properties": {
                      "tag_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 60
                      }
                    }
                  }
                },
                "required": [
                  "contact_ids",
                  "tag"
                ]
              },
              "example": {
                "contact_ids": [
                  "22222222-0000-4000-8000-000000000001",
                  "22222222-0000-4000-8000-000000000002"
                ],
                "tag": {
                  "name": "campaña-primavera"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ tagged, tag_id }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tagged": 2,
                    "tag_id": "17171717-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/contacts/{id}/tags": {
      "get": {
        "summary": "Tags on a contact",
        "description": "The tags attached to one contact, with a `total`. These are the same tags `/tags` manages and the same ones conversations use — one taxonomy, several things it can be attached to. `name` is the slug and `display_name` is what a person typed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags on the contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "17171717-0000-4000-8000-000000000002",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "tag_id": "17171717-0000-4000-8000-000000000001",
                      "name": "vip",
                      "display_name": "VIP",
                      "created_at": "2026-09-22T11:15:03.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Tag a contact",
        "description": "Send **exactly one** of `tag_id` (attach an existing tag) or `name` (resolve by slug, creating the tag if it is new). Sending both, or neither, is a 400.\n\nIdempotent — attaching a tag the contact already has changes nothing. Answers **201 with the contact’s complete tag list**, not just the tag added, so there is no need to re-read.\n\nWatch the by-name form: because it creates, a typo silently adds a new tag to the workspace rather than failing. Pass `tag_id` where the tag is meant to be known.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tag_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  }
                }
              },
              "example": {
                "name": "vip"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The contact’s tags after the attach",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "17171717-0000-4000-8000-000000000002",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "tag_id": "17171717-0000-4000-8000-000000000001",
                      "name": "vip",
                      "display_name": "VIP",
                      "created_at": "2026-09-22T11:15:03.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/tags/{tagId}": {
      "delete": {
        "summary": "Untag a contact",
        "description": "Removes the attachment only — the tag stays in the workspace and on every other contact. Removing one that was not attached is a 404, so this call does confirm something was removed. To delete the tag itself, use `DELETE /tags/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "tagId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/contacts/{id}/channels": {
      "get": {
        "summary": "A contact’s linked channel identities",
        "description": "The durable handles a contact is reachable on — a phone, a WhatsApp number, an Instagram id, an email address. This table, not `contact.phone`, is what an inbound message is resolved against, and it is what survives a merge: merging moves these rows onto the survivor so future inbound on any of them lands on the right person.\n\n`verified` says the PROVIDER confirmed the identity, not that a human believed it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Linked channels",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "16161616-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "kind": "whatsapp",
                      "identifier": "+56912345678",
                      "label": "Personal",
                      "verified": true,
                      "created_at": "2026-09-22T11:16:20.000Z",
                      "updated_at": "2026-09-22T11:16:20.000Z",
                      "contact_id": "22222222-0000-4000-8000-000000000001"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Link a channel identity to a contact",
        "description": "Adds one handle. `kind` is one of `email|phone|whatsapp|instagram|messenger|web|sms|tiktok`; `identifier` is the raw handle as the channel spells it (E.164 for phone-shaped kinds, the `@handle` or the provider id for social).\n\nLinking is what makes a person’s next message arrive as THEM instead of as a stranger: an address a person mentioned mid-chat is only an attribute until it is a channel row.\n\nPass `verified: true` only when a provider confirmed the identity — this is a claim about evidence, not about confidence.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "email",
                      "phone",
                      "whatsapp",
                      "instagram",
                      "messenger",
                      "web",
                      "sms",
                      "tiktok"
                    ]
                  },
                  "identifier": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 254
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "verified": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "kind",
                  "identifier"
                ]
              },
              "example": {
                "kind": "whatsapp",
                "identifier": "+56912345678",
                "label": "Personal",
                "verified": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The linked channel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "16161616-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "kind": "whatsapp",
                    "identifier": "+56912345678",
                    "label": "Personal",
                    "verified": true,
                    "created_at": "2026-09-22T11:16:20.000Z",
                    "updated_at": "2026-09-22T11:16:20.000Z",
                    "contact_id": "22222222-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/channels/{channelId}": {
      "delete": {
        "summary": "Unlink a channel identity",
        "description": "Removes the handle from the contact. The next inbound message on that handle will no longer resolve to this person and will create a new contact instead — which is the point when a handle was linked to the wrong one, and the cost when it was not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "channelId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Set a channel identity’s verified flag",
        "description": "The only editable field on a linked channel. To correct the handle itself, remove the row and link the right one — an identifier that changes under an id is how a message ends up on the wrong person.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "channelId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "verified": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "verified"
                ]
              },
              "example": {
                "verified": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The channel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "16161616-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "kind": "whatsapp",
                    "identifier": "+56912345678",
                    "label": "Personal",
                    "verified": true,
                    "created_at": "2026-09-22T11:16:20.000Z",
                    "updated_at": "2026-09-22T11:16:20.000Z",
                    "contact_id": "22222222-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/notes": {
      "get": {
        "summary": "A contact’s internal notes",
        "description": "Free text a team leaves about a person, newest first. Internal: a note is never shown to the contact and is never sent anywhere. `author_name` is a snapshot taken when the note was written, so renaming a member does not rewrite their notes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The contact’s notes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "18181818-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "author_user_id": "11111111-0000-4000-8000-000000000001",
                      "author_name": "Camila Rojas",
                      "body": "Prefiere que la contacten por la tarde.",
                      "created_at": "2026-09-22T11:17:44.000Z",
                      "updated_at": "2026-09-22T11:17:44.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a note to a contact",
        "description": "Records one internal note. It appears in the contact’s timeline as a `note` row.\n\nNotes are for what the team needs to remember about a person («prefiere que la contacten por la tarde»). Structured facts belong in a custom attribute, where they can be filtered and typed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  }
                },
                "required": [
                  "body"
                ]
              },
              "example": {
                "body": "Prefiere que la contacten por la tarde."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created note",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "18181818-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_name": "Camila Rojas",
                    "body": "Prefiere que la contacten por la tarde.",
                    "created_at": "2026-09-22T11:17:44.000Z",
                    "updated_at": "2026-09-22T11:17:44.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/notes/{noteId}": {
      "delete": {
        "summary": "Delete a contact note",
        "description": "Removes the note for good, and with it its timeline row. There is no archive for a note.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "noteId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a contact note",
        "description": "Replaces the note’s body. The author snapshot and `created_at` are unchanged — an edit is not a new note, and the timeline keeps its original place.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "noteId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  }
                },
                "required": [
                  "body"
                ]
              },
              "example": {
                "body": "Prefiere que la contacten después de las 16:00."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated note",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "18181818-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_name": "Camila Rojas",
                    "body": "Prefiere que la contacten después de las 16:00.",
                    "created_at": "2026-09-22T11:17:44.000Z",
                    "updated_at": "2026-09-22T11:19:02.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/outbound-preferences": {
      "get": {
        "summary": "A contact’s scope-aware consent / do-not-contact ledger",
        "description": "Append-only history, newest fact first — this is the surface that can answer «since when, and how do you know».\n\nEach row is one fact about one `channel` (`whatsapp|email|voice|instagram|messenger|any`) in one `scope` (`marketing|service|promised_followup|all_proactive`). A marketing block does NOT block service messages; a current `all_proactive` block outranks every scope.\n\nA superseded row stays readable rather than being overwritten, so the evidence trail survives the revocation.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The ledger, newest fact first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "19191919-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "channel": "email",
                      "scope": "marketing",
                      "status": "blocked",
                      "source": "admin",
                      "evidence_message_id": null,
                      "legal_basis": "Pidió por escrito no recibir promociones",
                      "recorded_at": "2026-09-22T11:20:01.000Z",
                      "expires_at": null,
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T11:20:01.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record an outbound preference (consent or DNC) for a contact",
        "description": "Appends one fact. **Revoking a block your team set is a POST with the opposite `status`** — the superseded row stays readable as evidence, which is why there is no PATCH or DELETE here.\n\nAn opt-out the **customer** asked for (their message, an unsubscribe link, their own «BAJA») cannot be revoked here: any `status` other than `blocked` that would end it answers 409 with `details.code` `customer_opt_out_lift_required`. Lifting it takes a team member in the Vitrina app, from the contact's card, after confirming.\n\n`recorded_at` is stamped by the server and is deliberately not settable: a caller who could backdate a consent fact could manufacture evidence. `legal_basis` is free text recorded verbatim («consentimiento explícito, Ley 21.719») and `evidence_message_id` points at the customer message that proves it, when there is one.\n\nA `blocked` fact here is enforced by the política de envíos: a later send to this contact in that scope is refused with a Bloqueo, not silently dropped.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "email",
                      "voice",
                      "instagram",
                      "messenger",
                      "any"
                    ]
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "marketing",
                      "service",
                      "promised_followup",
                      "all_proactive"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "allowed",
                      "blocked",
                      "unknown"
                    ]
                  },
                  "legal_basis": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "evidence_message_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "channel",
                  "scope",
                  "status"
                ]
              },
              "example": {
                "channel": "email",
                "scope": "marketing",
                "status": "blocked",
                "legal_basis": "Pidió por escrito no recibir promociones"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded fact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "19191919-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "channel": "email",
                    "scope": "marketing",
                    "status": "blocked",
                    "source": "admin",
                    "evidence_message_id": null,
                    "legal_basis": "Pidió por escrito no recibir promociones",
                    "recorded_at": "2026-09-22T11:20:01.000Z",
                    "expires_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T11:20:01.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/attributes": {
      "get": {
        "summary": "Custom attributes on a contact, merged with their definitions",
        "description": "The workspace’s own fields on this person. Values are merged with the `custom_attribute` DEFINITIONS (`entity_type = contact`), so one call is enough to render typed inputs: `label`, `data_type`, `options`, `required` and `multiline` come from the definition, `value` and `source` from the stored row.\n\nA defined-but-unset attribute is returned with `id: null` and `value: null` — that is how a client knows to draw an empty field rather than nothing at all.\n\nA `file`-typed row carries `file_limits` (the byte cap and the effective mime prefixes) straight off the server, so a client never hardcodes them. Treat the list as a picker HINT: the upload sniffs magic bytes and can still refuse a file the OS declared acceptable.\n\nThis is where a vertical lives. The shape of a contact is the same everywhere; «presupuesto», «previsión» or «curso» are attributes a workspace defined.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Values merged with definitions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "20202020-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "key": "presupuesto",
                      "value": "18000000",
                      "source": "admin",
                      "source_tool": null,
                      "updated_at": "2026-09-22T11:21:30.000Z",
                      "custom_attribute_id": "33333333-0000-4000-8000-000000000009",
                      "label": "Presupuesto",
                      "data_type": "number",
                      "options": [],
                      "required": false,
                      "multiline": false,
                      "unique_values": false,
                      "file_limits": null,
                      "pinned": false
                    },
                    {
                      "id": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "key": "contrato_firmado",
                      "value": null,
                      "source": null,
                      "source_tool": null,
                      "updated_at": null,
                      "custom_attribute_id": "33333333-0000-4000-8000-00000000000a",
                      "label": "Contrato firmado",
                      "data_type": "file",
                      "options": [],
                      "required": false,
                      "multiline": false,
                      "unique_values": false,
                      "file_limits": {
                        "max_bytes": 26214400,
                        "mime_prefixes": [
                          "application/pdf",
                          "image/"
                        ],
                        "accept": ".pdf,image/*"
                      },
                      "pinned": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Upsert custom attributes on a contact",
        "description": "Writes the attributes you send and leaves every other one alone — it is an upsert per key, not a replace of the set. To clear one, use `DELETE /contacts/{id}/attributes/{key}`.\n\nA key with no definition is still stored (that is how an AI tool captures something the workspace has not modelled yet), and appears in the GET above with `label` and `data_type` null until somebody defines it through `POST /custom-attributes`.\n\nA `file`-typed attribute is NOT written here — its bytes go through `POST /contacts/{id}/attributes/{key}/file`.\n\nThe response is the raw stored rows, without the definition merge: the caller already knows what it wrote.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "attributes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
                        },
                        "value": {
                          "type": "string",
                          "description": "Any JSON value (string, number, boolean, null, array, object)"
                        }
                      },
                      "required": [
                        "key"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 50
                  }
                },
                "required": [
                  "attributes"
                ],
                "additionalProperties": false
              },
              "example": {
                "attributes": [
                  {
                    "key": "presupuesto",
                    "value": "18000000"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored attribute rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "20202020-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "key": "presupuesto",
                      "value": "18000000",
                      "source": "admin",
                      "source_tool": null,
                      "created_at": "2026-09-22T11:21:30.000Z",
                      "updated_at": "2026-09-22T11:21:30.000Z",
                      "is_unique": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/attributes/{key}": {
      "delete": {
        "summary": "Delete one custom attribute from a contact",
        "description": "Clears the value for that key on this contact. The DEFINITION stays in the workspace and the attribute stays set on every other contact — to retire the field itself, use `DELETE /custom-attributes/{id}`.\n\nFor a `file`-typed attribute this also retires the stored document. Deleting a key the contact never had is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
            },
            "required": true,
            "name": "key",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/contacts/{id}/attributes/{key}/file": {
      "get": {
        "summary": "Download a contact’s stored attribute file",
        "description": "Streams the stored bytes back through the API rather than handing out a presigned URL, so the workspace check runs on every read, the object key never leaves the server, and the download is audited — who pulled which file, which matters when the file is a person’s document.\n\nThe response is served with `nosniff`, `Cache-Control: private, no-store` and a sandbox CSP, and anything whose stored mime is not a safe, previewable type comes back as `application/octet-stream` with `Content-Disposition: attachment` — a stored file never executes on our origin.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
            },
            "required": true,
            "name": "key",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file bytes",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a file into a contact’s file attribute",
        "description": "`multipart/form-data` with one `file` part, at most 25 MB. The attribute must already be defined for `entity_type = contact` with `data_type = file`; a key that is not defined is a 404 and a key that is defined as something else is a 400, so a typo can never overwrite a text attribute with a file descriptor.\n\nThe bytes go to a private bucket and the attribute value becomes a DESCRIPTOR — `{ key, filename, mime, size, uploaded_at }` — never a URL. The mime is sniffed from the bytes, not trusted from the request.\n\nOne file per attribute: uploading again REPLACES the stored document and the replacement is recorded in the audit trail.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
            },
            "required": true,
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              },
              "example": {
                "file": "contrato.pdf"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "cedula.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored file descriptor",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "key": "contrato_firmado",
                    "filename": "contrato.pdf",
                    "mime": "application/pdf",
                    "size": 182334,
                    "uploaded_at": "2026-09-22T11:22:10.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/conversations": {
      "get": {
        "summary": "A contact’s conversations across every channel",
        "description": "Every thread this person has had, newest activity first. Each carries its `lastMessage` (the newest real message — activity pills are bookkeeping, not conversation), a `last_message_at` alias of `last_message_date`, its ticket, and its `attributes`.\n\n**`meta.total` is the contact’s FULL count**, not the page size — use `limit`/`offset` to reach the rest. A contact with 221 threads used to report «total: 50».\n\nThis read is not narrowed by row-level visibility: the ficha shows a person’s whole history to anyone who may read the contact, which is what stops two people selling to the same customer twice.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The contact’s conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "display_id": "C-3052",
                      "channel": "whatsapp",
                      "status": "open",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "last_message_date": "2026-09-21T16:41:02.000Z",
                      "last_message_at": "2026-09-21T16:41:02.000Z",
                      "lastMessage": {
                        "id": "12121212-0000-4000-8000-000000000001",
                        "content": "¿Sigue disponible?",
                        "sender_type": "contact",
                        "created_at": "2026-09-21T16:41:02.000Z"
                      },
                      "ticket": null,
                      "attributes": []
                    }
                  ],
                  "meta": {
                    "total": 4,
                    "limit": 25,
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/timeline": {
      "get": {
        "summary": "One chronological feed of everything that happened to a contact",
        "description": "Merges ten families that already exist — `conversation`, `lead`, `lead_activity`, `appointment`, `note` (contact notes), `recontacto` (outbound attempts), `hold` (safety holds), `ticket`, `audit` (operator actions on this contact) and `ad_touch` (a HISTORICAL ad click another platform observed before Vitrina, from a historical contact import: `title` is the ad’s headline, `status` the platform, `channel` the one the ad opened; never a conversation) — into one feed, newest first.\n\n**There is no contact-event table and this does not add one.** Every row is read from the table that already owns it, so nothing has to be backfilled and no second write path can drift.\n\n`kind` is a comma-separated list (`kind=conversation,lead,appointment`) and only the requested families are queried — it is a narrowing of the read, not a filter over the whole union. An unrecognised token is a 400 naming it.\n\n`title` is deliberately key-ish where the source is: a `lead_activity` title is its raw `kind` (`interest_changed`), which the client maps to a label. `ref_id` is the record you can open — for `lead_activity` that is the LEAD, since the activity’s own id is a bigint nothing links to. `data` carries `lead_activity.data` whole, and on `ad_touch` the ad’s ids (`ad_id`, `ad_set_id`, `campaign_id`), `source` and `historical: true`; it is null for every other kind.\n\n`actor.kind` is `person` (a resolved teammate), `ai_agent`, `api` (an integration credential) or `system`. It is derived from what the row stores — a user id, the author snapshot a history row keeps (ADR 0106 §3.1), or a token such as `conversation.handler` / `lead.source` / `audit_log.principal_kind` — never guessed. An `api` actor carries the API key id and its name as it was when it acted («CRM (API)»); a `person` who acted through a connected app or a personal token carries `actor.via` `{ kind, name }` («Camila vía Claude»).\n\n`meta.has_more` comes from reading one row past the page, so it is exact even when the total is a multiple of `limit`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": false,
            "name": "kind",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Timeline rows, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "ts": "2026-09-22T11:20:01.000Z",
                      "kind": "audit",
                      "title": "contacts.outbound_preferences.record",
                      "detail": null,
                      "ref_id": "21212121-0000-4000-8000-000000000001",
                      "display_id": null,
                      "status": null,
                      "channel": null,
                      "actor": {
                        "kind": "api",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM (API)"
                      },
                      "data": null
                    },
                    {
                      "ts": "2026-09-22T11:17:44.000Z",
                      "kind": "note",
                      "title": "note",
                      "detail": "Prefiere que la contacten por la tarde.",
                      "ref_id": "18181818-0000-4000-8000-000000000001",
                      "display_id": null,
                      "status": null,
                      "channel": null,
                      "actor": {
                        "kind": "person",
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Camila Rojas"
                      },
                      "data": null
                    },
                    {
                      "ts": "2026-09-21T16:40:12.000Z",
                      "kind": "conversation",
                      "title": "whatsapp · open",
                      "detail": null,
                      "ref_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "display_id": "C-3052",
                      "status": "open",
                      "channel": "whatsapp",
                      "actor": {
                        "kind": "ai_agent",
                        "id": null,
                        "name": null
                      },
                      "data": null
                    }
                  ],
                  "meta": {
                    "limit": 50,
                    "offset": 0,
                    "has_more": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/attachments": {
      "get": {
        "summary": "Files exchanged with a contact across every conversation",
        "description": "**One row per FILE, not per message.** Media lives in `message.media_urls`, a jsonb array, so a turn that carried three photos is three rows sharing one `message_id`.\n\n⚠ **Key the icon off `kind`, never off `mime`.** `kind` is derived from `message.type` (`image` / `audio` / `video` pass through; anything else is `document` when the mime says `application/*` and `file` otherwise). `mime` and `size` are read from `metadata` on a best-effort basis and are null on effectively every row today — no inbound path fills them.\n\n`media_sensitivity: \"sensitive\"` marks media the sensitive-media policy withheld from the model; clients render it behind a reveal.\n\n`meta.has_more` comes from a `limit + 1` probe.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Contacts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 40
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Attachments, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "url": "https://media.vitrinadev.com/t/abc/1f2e3d.jpg",
                      "kind": "image",
                      "mime": null,
                      "size": null,
                      "media_sensitivity": null,
                      "message_id": "12121212-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "created_at": "2026-09-21T16:41:02.000Z"
                    }
                  ],
                  "meta": {
                    "limit": 40,
                    "offset": 0,
                    "has_more": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/companies": {
      "get": {
        "summary": "List the workspace’s companies",
        "description": "Every company in alphabetical order, each with a `contact_count` — how many LIVE contacts name it (an archived or merged contact is not counted). `q` narrows the LIST by name; it does not narrow the counts, which describe the company rather than the search.\n\nThis list does not page and carries no `meta`: a workspace has companies, not hundreds of thousands of them. If that stops being true for a customer, it is a real change here rather than a `limit` they can guess at.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Companies"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace’s companies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "15151515-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Constructora Andes SpA",
                      "domain": "andes.cl",
                      "industry": "Construcción",
                      "size": "51-200",
                      "website": "https://andes.cl",
                      "phone": "+56223456789",
                      "notes": "Cuenta corporativa — factura a fin de mes.",
                      "metadata": {},
                      "created_at": "2026-09-20T09:12:00.000Z",
                      "updated_at": "2026-09-20T09:12:00.000Z",
                      "contact_count": 7
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a company",
        "description": "Only `name` is required. Nothing here is unique or validated against anything: two companies may share a `domain`, and creating the same name twice creates two rows — there is no dedup and no merge for companies, unlike contacts.\n\n`metadata` is a free object an integration can use to keep its own ids and fields; it is stored verbatim and never interpreted.\n\nCreating a company links nobody to it. Attach contacts afterwards with `PATCH /contacts/{id}` and `company_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Companies"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "domain": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "size": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "website": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Constructora Andes SpA",
                "domain": "andes.cl",
                "industry": "Construcción",
                "size": "51-200",
                "website": "https://andes.cl",
                "phone": "+56223456789"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created company",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "15151515-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Constructora Andes SpA",
                    "domain": "andes.cl",
                    "industry": "Construcción",
                    "size": "51-200",
                    "website": "https://andes.cl",
                    "phone": "+56223456789",
                    "notes": "Cuenta corporativa — factura a fin de mes.",
                    "metadata": {},
                    "created_at": "2026-09-20T09:12:00.000Z",
                    "updated_at": "2026-09-20T09:12:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/companies/{id}": {
      "get": {
        "summary": "Get a company",
        "description": "One company by uuid. A company from another workspace is a 404, never a 403 — the API does not confirm that an id it will not serve exists.\n\nThe single read does not carry `contact_count`; the list does. To get the people, ask `GET /contacts/search?company_id=<uuid>`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Companies"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The company",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15151515-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Constructora Andes SpA",
                    "domain": "andes.cl",
                    "industry": "Construcción",
                    "size": "51-200",
                    "website": "https://andes.cl",
                    "phone": "+56223456789",
                    "notes": "Cuenta corporativa — factura a fin de mes.",
                    "metadata": {},
                    "created_at": "2026-09-20T09:12:00.000Z",
                    "updated_at": "2026-09-20T09:12:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a company",
        "description": "A PUT that behaves as a PATCH: send only the fields you are changing — at least one is required, and everything you leave out is kept. `null` clears a nullable field.\n\n`metadata` is REPLACED as a whole when you send it, not merged key by key. Read it, change what you need, and write the complete object back.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Companies"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "domain": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "industry": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "size": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "website": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 5000
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                }
              },
              "example": {
                "industry": "Construcción e inmobiliaria",
                "size": "201-500"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated company",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15151515-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Constructora Andes SpA",
                    "domain": "andes.cl",
                    "industry": "Construcción e inmobiliaria",
                    "size": "201-500",
                    "website": "https://andes.cl",
                    "phone": "+56223456789",
                    "notes": "Cuenta corporativa — factura a fin de mes.",
                    "metadata": {},
                    "created_at": "2026-09-20T09:12:00.000Z",
                    "updated_at": "2026-09-22T11:30:18.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a company",
        "description": "⚠ **Permanent — there is no archive for a company.** The contacts survive: `contact.company_id` is set to null, so the people stay and only the grouping is gone. Nothing else references a company, so nothing else breaks.\n\nDeleting a company from another workspace is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Companies"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/billing/usage/live": {
      "get": {
        "summary": "Usage of a month: daily meters, amount, month-end estimate, history",
        "description": "Without `period`, the month in progress priced by the same meters and rate card the monthly close uses (what the next close would bill), in UF and in CLP at today's UF; a month-end `projection` that is an estimate (linear by elapsed Santiago days); every add-on and its inclusion reason; and `history`: past months as the ISSUED invoice amounts (never recomputed), months invoiced outside the system labelled `external`. `mode` says how to read it: fixed-contract workspaces see their fixed price with usage at $0, not-billed workspaces see usage without prices (`show_prices` false), and a workspace whose billing has not started yet gets `billing_starts_on`.\n\nWith `period=YYYY-MM` (a past Santiago month, #3760) the same shape describes that month: its meters over the whole month, and for the amount only what was billed — the issued cobro's frozen `lines` and `totals` with its frozen UF (`amount_source: invoice`), a month invoiced by hand (`external`) or nothing (`none`, usage only); never today's prices. A cobro that froze its billed set counts conversations from it (`conversations_source: invoice`), so the count equals the cobro's. `projection` is null for a past month. A future month is a 400.\n\n`daily` carries every metric per Santiago day (conversations, voice and forwarded minutes, billed agent-eval runs, email-campaign sends, per-unit add-on usage); each series' `total` is the sum of its days. `GET /billing/usage/export` is the same as CSV.\n\n`next_charge` (current month only; null for a past month or when prices are hidden) is the cobro the close will issue: its cut-off, issue and charge dates, whether it is charged automatically or paid by transfer, and its lines in pesos (so far and at month end) exactly as the hosted cobro will show them. Counts and money only, never conversation content. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Month, `YYYY-MM` (America/Santiago). Default: the current month. A future month is a 400."
            },
            "required": false,
            "description": "Month, `YYYY-MM` (America/Santiago). Default: the current month. A future month is a 400.",
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage statement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/LiveUsage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-10-07T15:00:00.000Z",
                    "period": {
                      "month": "2026-09",
                      "start": "2026-09-01T04:00:00.000Z",
                      "end": "2026-10-01T03:00:00.000Z",
                      "elapsed_days": 30,
                      "total_days": 30,
                      "closed": true
                    },
                    "mode": "metered",
                    "billing_starts_on": null,
                    "show_prices": true,
                    "uf": {
                      "clp": 39485.65,
                      "date": "2026-10-01",
                      "stale": false
                    },
                    "usage": {
                      "conversations": 3,
                      "voice_minutes": 4,
                      "forwarded_voice_minutes": 0,
                      "eval_runs": 0,
                      "email_marketable_contacts": 0,
                      "email_marketing_sent": 0
                    },
                    "lines": [
                      {
                        "kind": "conversations",
                        "count": 3,
                        "uf_unit": 0.012,
                        "uf_subtotal": 0.036
                      },
                      {
                        "kind": "voice_minutes",
                        "count": 4,
                        "uf_unit": 0.006,
                        "uf_subtotal": 0.024
                      }
                    ],
                    "totals": {
                      "subtotal_uf": 0.06,
                      "clp_net": 2369,
                      "iva_amount": 450,
                      "clp_total": 2819
                    },
                    "projection": null,
                    "next_charge": null,
                    "amount_source": "invoice",
                    "invoice": {
                      "id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "number": "INV-00000000-202609",
                      "status": "paid",
                      "issued_at": "2026-10-01T09:00:00.000Z"
                    },
                    "conversations_source": "invoice",
                    "daily": {
                      "days": [
                        "2026-09-01",
                        "2026-09-02",
                        "2026-09-03"
                      ],
                      "series": [
                        {
                          "metric": "conversations",
                          "feature": null,
                          "total": 3,
                          "values": [
                            1,
                            0,
                            2
                          ]
                        },
                        {
                          "metric": "voice_minutes",
                          "feature": null,
                          "total": 4,
                          "values": [
                            0,
                            4,
                            0
                          ]
                        },
                        {
                          "metric": "forwarded_voice_minutes",
                          "feature": null,
                          "total": 0,
                          "values": [
                            0,
                            0,
                            0
                          ]
                        },
                        {
                          "metric": "eval_runs",
                          "feature": null,
                          "total": 0,
                          "values": [
                            0,
                            0,
                            0
                          ]
                        },
                        {
                          "metric": "email_marketing_sent",
                          "feature": null,
                          "total": 0,
                          "values": [
                            0,
                            0,
                            0
                          ]
                        }
                      ]
                    },
                    "addons": [],
                    "history": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/usage/export": {
      "get": {
        "summary": "Export the daily usage of one month (CSV)",
        "description": "The `daily` block of `GET /billing/usage/live` for the same `period` as UTF-8 CSV (with BOM): one row per Santiago day (`fecha`), one column per metric (`conversaciones`, `minutos_voz`, `minutos_desvio`, `correos_marketing`, and `uso_<feature>` per add-on), then a `total` row equal to the totals the page shows. Counts only, never conversation content; the billed conversations themselves are `GET /billing/conversations/export`. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Month, `YYYY-MM` (America/Santiago). Default: the current month. A future month is a 400."
            },
            "required": false,
            "description": "Month, `YYYY-MM` (America/Santiago). Default: the current month. A future month is a 400.",
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV attachment",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/conversations": {
      "get": {
        "summary": "Billed conversations of one billing month (paginated)",
        "description": "Every conversation that counted toward the month: its billable moment, channel, the contact masked to the last 4 phone digits, its review state and, for a month an invoice billed, whether a later review exempted it (credited on a later invoice). A billed month reads the set the invoice froze at the close (`period.source = invoice`), so `summary.count` equals the invoice’s conversations; any other month reads the live meter. Never returns message text. Keyset-paginated (`next_cursor`). Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, `YYYY-MM` (America/Santiago). Default: the current month."
            },
            "required": false,
            "description": "Billing month, `YYYY-MM` (America/Santiago). Default: the current month.",
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Opaque cursor from the previous page’s `next_cursor`."
            },
            "required": false,
            "description": "Opaque cursor from the previous page’s `next_cursor`.",
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "description": "Page size (default 100, max 500)."
            },
            "required": false,
            "description": "Page size (default 100, max 500).",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of billed conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BilledConversationsPage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": {
                      "month": "2026-10",
                      "start": "2026-10-01T03:00:00.000Z",
                      "end": "2026-11-01T03:00:00.000Z",
                      "closed": true,
                      "source": "invoice"
                    },
                    "invoice": {
                      "id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "number": "INV-00000000-202610",
                      "status": "issued",
                      "issued_at": "2026-11-01T09:00:00.000Z"
                    },
                    "summary": {
                      "count": 2,
                      "unreviewed": 1,
                      "exempted_after_close": 1,
                      "conversations_uf": 0.024
                    },
                    "items": [
                      {
                        "conversation_id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "display_id": "C-1001",
                        "first_billable_at": "2026-10-03T14:12:09.000Z",
                        "channel": "whatsapp",
                        "contact_last4": "4321",
                        "review_state": "billable",
                        "counted_because": "customer_wrote_agent_replied",
                        "exempted_after_close": null
                      },
                      {
                        "conversation_id": "c1c1c1c1-0000-4000-8000-000000000002",
                        "display_id": "C-1002",
                        "first_billable_at": "2026-10-29T22:40:51.000Z",
                        "channel": "instagram",
                        "contact_last4": null,
                        "review_state": "unreviewed",
                        "counted_because": "customer_wrote_agent_replied",
                        "exempted_after_close": {
                          "reason": "El cliente solo saludó y no volvió a responder.",
                          "exempted_at": "2026-11-02T10:00:00.000Z",
                          "credited_on_invoice": null
                        }
                      }
                    ],
                    "next_cursor": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/conversations/{conversationId}/dispute": {
      "post": {
        "summary": "Report («Reportar») a billed conversation",
        "description": "The workspace disputes ONE conversation its cobro billed, with a reason code (`not_real_customer`, `duplicate`, `answered_by_human`, `other`) and an optional comment. One open dispute per conversation (409 otherwise). A dispute never blocks or delays the automatic charge: an approved one is discounted on the next invoice (or refunded). The founder is notified. The conversation list (`GET /billing/conversations`) carries each conversation’s latest dispute and its status. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "conversationId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason_code": {
                    "type": "string",
                    "enum": [
                      "not_real_customer",
                      "duplicate",
                      "answered_by_human",
                      "other"
                    ]
                  },
                  "comment": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason_code"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason_code": "duplicate",
                "comment": "Es la misma persona de ayer."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The open dispute",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ConversationDispute"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "d1d1d1d1-0000-4000-8000-000000000001",
                    "conversation_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "status": "open",
                    "reason_code": "duplicate",
                    "reason_label": "Conversación duplicada",
                    "comment": "Es la misma persona de ayer.",
                    "resolution": null,
                    "resolution_note": null,
                    "created_at": "2026-10-05T14:00:00.000Z",
                    "resolved_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/conversations/export": {
      "get": {
        "summary": "Export the billed conversations of one month (CSV)",
        "description": "The same rows as `GET /billing/conversations`, the whole month, streamed as UTF-8 CSV (with BOM). Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Billing month, `YYYY-MM` (America/Santiago). Default: the current month."
            },
            "required": false,
            "description": "Billing month, `YYYY-MM` (America/Santiago). Default: the current month.",
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV attachment",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/history": {
      "get": {
        "summary": "Billing history: one row per billed month («Historial»)",
        "description": "Every cobro the workspace was issued (drafts and voided ones excluded) and every month invoiced outside the system, together, newest month first. Each row carries the cobro number, neto, IVA and total in whole CLP, the client-facing status, the paid date, the month’s conversation count (the same count `GET /billing/conversations?period=` reports) and its documents: the cobro PDF, and — once the cobro is paid and the factura electrónica emitted — the factura folio with its PDF and XML. A cobro issued by hand for a month billed outside the monthly close is an ordinary cobro row on the month it was issued for; another month it also billed reads `kind: manual` with that cobro in `billed_in` and its status and documents, but no amount (the amount is on the cobro’s own row). A month invoiced by hand with no cobro on record reads `kind: manual`, `status: billed_outside` and no amount. `filter` = `all` | `paid` | `open`; 12 rows per page, keyset-paginated (`next_cursor`). Requires `billing:read` (owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "paid",
                "open"
              ],
              "description": "`all` (default), `paid` (Pagadas) or `open` (Pendientes: por pagar or vencido)."
            },
            "required": false,
            "description": "`all` (default), `paid` (Pagadas) or `open` (Pendientes: por pagar or vencido).",
            "name": "filter",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 300,
              "description": "Opaque cursor from the previous page’s `next_cursor` («Ver más»)."
            },
            "required": false,
            "description": "Opaque cursor from the previous page’s `next_cursor` («Ver más»).",
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "description": "Rows per page (default 12, max 100)."
            },
            "required": false,
            "description": "Rows per page (default 12, max 100).",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the billing history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BillingHistoryPage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "filter": "all",
                    "items": [
                      {
                        "row_key": "b1b1b1b1-0000-4000-8000-000000000001",
                        "kind": "invoice",
                        "period": "2026-10",
                        "invoice_id": "b1b1b1b1-0000-4000-8000-000000000001",
                        "number": "VT-2026-0007",
                        "status": "paid",
                        "clp_net": 150000,
                        "iva_amount": 28500,
                        "clp_total": 178500,
                        "issued_at": "2026-11-01T12:00:00.000Z",
                        "due_date": "2026-11-05",
                        "paid_at": "2026-11-03T15:20:00.000Z",
                        "paid_date": "2026-11-03",
                        "conversation_count": 412,
                        "payable": false,
                        "billed_in": null,
                        "concept": null,
                        "documents": {
                          "cobro_pdf_path": "/api/v1/billing/invoices/b1b1b1b1-0000-4000-8000-000000000001/pdf",
                          "factura_state": "issued",
                          "factura": {
                            "folio": "1042",
                            "pdf_path": "/api/v1/billing/invoices/b1b1b1b1-0000-4000-8000-000000000001/factura/pdf",
                            "xml_path": "/api/v1/billing/invoices/b1b1b1b1-0000-4000-8000-000000000001/factura/xml"
                          }
                        }
                      },
                      {
                        "row_key": "manual:2026-09",
                        "kind": "manual",
                        "period": "2026-09",
                        "invoice_id": null,
                        "number": null,
                        "status": "billed_outside",
                        "clp_net": null,
                        "iva_amount": null,
                        "clp_total": null,
                        "issued_at": null,
                        "due_date": null,
                        "paid_at": null,
                        "paid_date": null,
                        "conversation_count": 388,
                        "payable": false,
                        "billed_in": null,
                        "concept": null,
                        "documents": {
                          "cobro_pdf_path": null,
                          "factura_state": "none",
                          "factura": null
                        }
                      }
                    ],
                    "next_cursor": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/history/export": {
      "get": {
        "summary": "Export the billing history (CSV)",
        "description": "Every row of `GET /billing/history` for the filter, one line per month, as UTF-8 CSV with BOM: period, kind, cobro number, neto, IVA, total, status, paid date, conversations, factura folio and the cobro PDF, factura PDF and factura XML links. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "paid",
                "open"
              ],
              "description": "`all` (default), `paid` (Pagadas) or `open` (Pendientes: por pagar or vencido)."
            },
            "required": false,
            "description": "`all` (default), `paid` (Pagadas) or `open` (Pendientes: por pagar or vencido).",
            "name": "filter",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "CSV attachment",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/profile": {
      "get": {
        "summary": "Tenant billing/tax identity (the DTE receptor)",
        "description": "«Datos de facturación»: who Vitrina invoices for this workspace — RUT, razón social, giro, address and comuna — and `billing_emails`, every address that receives the cobros, the payment reminders and collections emails, and the factura electrónica (first one = `billing_email`). `complete` says whether a factura can be issued with this data and `missing` lists what is still needed. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "responses": {
          "200": {
            "description": "Billing profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BillingProfile"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "legal_name": "Automotora Ejemplo SpA",
                    "rut": "76086428-5",
                    "giro": "Compraventa de vehículos",
                    "address": {
                      "line1": "Av. Apoquindo 1234",
                      "line2": "Of. 501",
                      "city": "Las Condes",
                      "region": "Metropolitana de Santiago",
                      "postal_code": null,
                      "country": "CL",
                      "comuna_code": "13114"
                    },
                    "billing_email": "pagos@cliente-ejemplo.cl",
                    "cc_emails": [
                      "contabilidad@cliente-ejemplo.cl"
                    ],
                    "billing_emails": [
                      "pagos@cliente-ejemplo.cl",
                      "contabilidad@cliente-ejemplo.cl"
                    ],
                    "complete": true,
                    "missing": [],
                    "source": "in_app",
                    "confirmed_at": "2026-10-07T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update the tenant billing/tax identity",
        "description": "Partial update of «Datos de facturación»: only the fields sent change. The RUT check digit, the SII lengths (razón social 100, giro 40, address 70) and the comuna are validated. `billing_emails` replaces the whole recipient list (1–10 addresses, in order; repeats dropped). Changes apply to the cobros and facturas issued from now on — an issued factura is never changed — and facturas waiting for this data are re-checked at once. Requires `billing:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "legal_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 256
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 32
                  },
                  "giro": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 256
                  },
                  "address": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "line1": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 256
                      },
                      "line2": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 256
                      },
                      "city": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 128
                      },
                      "region": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 128
                      },
                      "postal_code": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 32
                      },
                      "country": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 2
                      },
                      "comuna_code": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^[0-9]{5}$"
                      }
                    }
                  },
                  "billing_email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 320,
                    "format": "email"
                  },
                  "cc_emails": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "maxLength": 320,
                      "format": "email"
                    },
                    "maxItems": 10
                  },
                  "billing_emails": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "maxLength": 320,
                      "format": "email"
                    },
                    "maxItems": 10,
                    "description": "Every address that receives the cobros, the payment reminders and collections emails, and the factura electrónica, in order (#3764). Replaces `billing_email` (the first address) and `cc_emails` (the rest); `[]` or null clears them. Repeats are dropped, case-insensitively. Cannot be combined with `billing_email` / `cc_emails` in one request. Applies to cobros and facturas issued from now on; an issued factura is never changed."
                  }
                }
              },
              "example": {
                "billing_emails": [
                  "pagos@cliente-ejemplo.cl",
                  "contabilidad@cliente-ejemplo.cl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated billing profile",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BillingProfile"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "legal_name": "Automotora Ejemplo SpA",
                    "rut": "76086428-5",
                    "giro": "Compraventa de vehículos",
                    "address": {
                      "line1": "Av. Apoquindo 1234",
                      "line2": "Of. 501",
                      "city": "Las Condes",
                      "region": "Metropolitana de Santiago",
                      "postal_code": null,
                      "country": "CL",
                      "comuna_code": "13114"
                    },
                    "billing_email": "pagos@cliente-ejemplo.cl",
                    "cc_emails": [
                      "contabilidad@cliente-ejemplo.cl"
                    ],
                    "billing_emails": [
                      "pagos@cliente-ejemplo.cl",
                      "contabilidad@cliente-ejemplo.cl"
                    ],
                    "complete": true,
                    "missing": [],
                    "source": "in_app",
                    "confirmed_at": "2026-10-07T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/payment-method": {
      "get": {
        "summary": "Current payment provider + mandate status for the tenant",
        "description": "The workspace's chosen payment provider (`fintoc` for a PAC bank mandate, `mercadopago` for a tokenized card) and the status of its newest mandate: `active` means add-ons can be activated self-serve (`POST /entitlements/{feature}/activate`), `pending` means the Fintoc hosted setup was started but not finished. All three fields are `null` before any enrollment. Carries no payment instrument data — no card number, bank account or provider token is ever returned.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "responses": {
          "200": {
            "description": "Payment method status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentMethodStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "fintoc",
                    "status": "active",
                    "mandateId": "f1f1f1f1-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/payment-methods": {
      "get": {
        "summary": "The workspace’s payment methods, or how to pay by transfer",
        "description": "Every payment method with its rail (`fintoc_pac`, `fintoc_card`, `oneclick`, `mercadopago`) named by the rail that holds it, its display fields (PAC: bank + last digits + mandate status `activo` / `pendiente_firma` / `rechazado`; card: brand + last 4 + expiry + credit/debit when reported) and which one is the default the cobros are charged to. With no active method `pays_by` is `transfer` and `transfer` carries Vitrina’s bank account and the reference of the cobro to pay. Never returns a card number, a full account number or a provider token. Requires `billing:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "responses": {
          "200": {
            "description": "Payment methods",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentMethodsView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "methods": [
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000003",
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "status": "activo",
                        "is_default": true,
                        "bank": {
                          "name": "Banco de Chile",
                          "last4": "9212"
                        },
                        "card": null,
                        "removable": false,
                        "created_at": "2026-10-02T14:05:00.000Z"
                      },
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000004",
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "status": "activo",
                        "is_default": false,
                        "bank": null,
                        "card": {
                          "brand": "Visa",
                          "last4": "6623",
                          "exp_month": null,
                          "exp_year": null
                        },
                        "removable": true,
                        "created_at": "2026-09-15T10:00:00.000Z"
                      }
                    ],
                    "default_method_id": "f1f1f1f1-0000-4000-8000-000000000003",
                    "pays_by": "method",
                    "pending_cobro": {
                      "invoice_id": "f1f1f1f1-0000-4000-8000-000000000010",
                      "number": "INV-2026-10-0007",
                      "reference": "VT-7K2QM9",
                      "due_at": "2026-11-08T03:00:00.000Z"
                    },
                    "transfer": null,
                    "available_rails": [
                      {
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "recommended": true
                      },
                      {
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "recommended": false
                      }
                    ],
                    "pac_banks": [
                      {
                        "code": "cl_banco_de_chile",
                        "name": "Banco de Chile"
                      },
                      {
                        "code": "cl_banco_estado",
                        "name": "BancoEstado"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Start adding a payment method (PAC or card)",
        "description": "Starts the rail’s own enrolment and answers where to send the browser: `fintoc_pac` → a GET to Fintoc’s hosted PAC checkout; `fintoc_card` → a GET to Fintoc’s hosted card checkout (credit or debit; offered in `available_rails` only while Fintoc has the card product enabled for Vitrina, else refused with `fintoc_card_unavailable`); `oneclick` → a form POST of `TBK_TOKEN` to Transbank’s inscription page; `mercadopago` takes a card token minted by Mercado Pago’s SDK and is active at once (`redirect: null`). The provider sends the browser back to `return_url`, which must be on the app’s own origin. The current default stays in place until the new method is active; then it becomes the default and any outstanding cobro is retried on it right away. Requires `billing:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddPaymentMethodBody"
              },
              "example": {
                "rail": "oneclick",
                "return_url": "https://app.vitrinadev.com/w/agencia/settings/billing/invoices?medio=tarjeta"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Enrolment started (redirect) or completed (card token)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AddPaymentMethodResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "method_id": "f1f1f1f1-0000-4000-8000-000000000005",
                    "rail": "oneclick",
                    "status": "pendiente",
                    "redirect": {
                      "url": "https://webpay3g.transbank.cl/webpayserver/bp_multicode_inscription.cgi",
                      "method": "POST",
                      "fields": {
                        "TBK_TOKEN": "01ab23cd45ef67ab89cd01ef23ab45cd67ef89ab01cd23ef45ab67cd89ef01ab"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/billing/payment-methods/{id}/default": {
      "post": {
        "summary": "Make an active payment method the default",
        "description": "The cobros are charged to this method from now on, and any outstanding cobro (issued or past due, nothing in flight) is retried on it right away — once per cobro and method. Choosing the current default again changes nothing. 409 for a method that is not active. Answers the updated payment methods. Requires `billing:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The payment methods after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentMethodsView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "methods": [
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000003",
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "status": "activo",
                        "is_default": true,
                        "bank": {
                          "name": "Banco de Chile",
                          "last4": "9212"
                        },
                        "card": null,
                        "removable": false,
                        "created_at": "2026-10-02T14:05:00.000Z"
                      },
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000004",
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "status": "activo",
                        "is_default": false,
                        "bank": null,
                        "card": {
                          "brand": "Visa",
                          "last4": "6623",
                          "exp_month": null,
                          "exp_year": null
                        },
                        "removable": true,
                        "created_at": "2026-09-15T10:00:00.000Z"
                      }
                    ],
                    "default_method_id": "f1f1f1f1-0000-4000-8000-000000000003",
                    "pays_by": "method",
                    "pending_cobro": {
                      "invoice_id": "f1f1f1f1-0000-4000-8000-000000000010",
                      "number": "INV-2026-10-0007",
                      "reference": "VT-7K2QM9",
                      "due_at": "2026-11-08T03:00:00.000Z"
                    },
                    "transfer": null,
                    "available_rails": [
                      {
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "recommended": true
                      },
                      {
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "recommended": false
                      }
                    ],
                    "pac_banks": [
                      {
                        "code": "cl_banco_de_chile",
                        "name": "Banco de Chile"
                      },
                      {
                        "code": "cl_banco_estado",
                        "name": "BancoEstado"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/payment-methods/{id}": {
      "delete": {
        "summary": "Remove a payment method",
        "description": "Stops charging the method at once (a Oneclick card is also deleted at Transbank). 409 `default_method_has_pending_cobro` for the default while a cobro is pending — add or choose another method first — and 409 while a charge is in progress on the method. Removing the default with nothing pending hands the default to the next active method, if any. Answers the updated payment methods. Requires `billing:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The payment methods after the removal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentMethodsView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "methods": [
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000003",
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "status": "activo",
                        "is_default": true,
                        "bank": {
                          "name": "Banco de Chile",
                          "last4": "9212"
                        },
                        "card": null,
                        "removable": false,
                        "created_at": "2026-10-02T14:05:00.000Z"
                      },
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000004",
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "status": "activo",
                        "is_default": false,
                        "bank": null,
                        "card": {
                          "brand": "Visa",
                          "last4": "6623",
                          "exp_month": null,
                          "exp_year": null
                        },
                        "removable": true,
                        "created_at": "2026-09-15T10:00:00.000Z"
                      }
                    ],
                    "default_method_id": "f1f1f1f1-0000-4000-8000-000000000003",
                    "pays_by": "method",
                    "pending_cobro": {
                      "invoice_id": "f1f1f1f1-0000-4000-8000-000000000010",
                      "number": "INV-2026-10-0007",
                      "reference": "VT-7K2QM9",
                      "due_at": "2026-11-08T03:00:00.000Z"
                    },
                    "transfer": null,
                    "available_rails": [
                      {
                        "rail": "fintoc_pac",
                        "rail_name": "Débito automático (PAC)",
                        "recommended": true
                      },
                      {
                        "rail": "oneclick",
                        "rail_name": "Tarjeta (Webpay Oneclick)",
                        "recommended": false
                      }
                    ],
                    "pac_banks": [
                      {
                        "code": "cl_banco_de_chile",
                        "name": "Banco de Chile"
                      },
                      {
                        "code": "cl_banco_estado",
                        "name": "BancoEstado"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/invoices/{id}/transfer-claim": {
      "post": {
        "summary": "«Ya pagué por transferencia»: pause automatic charging of a cobro",
        "description": "The owner says this cobro was already paid by bank transfer (for example without its reference, which the reconciliation cannot match). While the claim is pending nothing charges the cobro automatically — not the scheduled charge, not a retry — and it does not advance in collections; the Vitrina team verifies the transfer and either records the payment or resumes the charge with a fresh heads-up. Idempotent (`created: false` when already pending). 409 when the cobro is no longer open (paid, voided, or a charge is in flight). Requires `billing:write`.\n\n**Optional comprobante** (#7094): send the proof of the transfer with the claim as a multipart `file` part (or the JSON file variant below). A PDF, JPG or PNG of at most 10 MB, type read from the bytes; a refused file (400 `comprobante_empty`, 413 `comprobante_too_large`, 415 `comprobante_type`) refuses the whole request and nothing is claimed. It is a claim, not proof: it changes nothing on the cobro. `comprobante` in the answer is the file THIS call attached (null without one). If the claim was made but the file could not be stored, the answer is still 200 with `comprobante: null` and `comprobante_error` {reason `comprobante_not_stored`, retryable true}: retry only the file. Add or replace it later with `POST …/transfer-claim/comprobante`.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "comprobante.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TransferClaimResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "created": true,
                    "claimedAt": "2026-10-14T13:20:00.000Z",
                    "number": "VT-2026-0002"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/payment-method/setup": {
      "post": {
        "summary": "Connect a payment provider (Fintoc hosted PAC redirect, or MercadoPago tokenized card)",
        "description": "Makes `provider` the workspace's single payment account and creates its mandate. Fintoc answers `status: \"pending\"` with a `setupUrl` to send the owner to (the hosted PAC setup, which returns to `returnUrl`); MercadoPago takes a `cardToken` minted client-side by MercadoPago's own SDK — never a raw card number — and answers `status: \"active\"` with `setupUrl: null`. `email` (the payer) is required by both providers; `rut` (optional) goes on the Fintoc customer. Requires `billing:write`, which only the workspace owner holds.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "provider": {
                    "type": "string",
                    "enum": [
                      "fintoc",
                      "mercadopago",
                      "transbank_oneclick"
                    ]
                  },
                  "returnUrl": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "cardToken": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 320,
                    "format": "email"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 256
                  },
                  "rut": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 32
                  },
                  "cancelUrl": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "Fintoc PAC: where the hosted enrolment sends the buyer when they cancel it (defaults to `returnUrl`)."
                  },
                  "bank": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "Fintoc PAC: the bank the client chose — a bank code from `pacBanks` on GET /billing/payment-method (e.g. `cl_banco_de_chile`). The hosted enrolment then opens on that bank."
                  }
                },
                "required": [
                  "provider"
                ]
              },
              "example": {
                "provider": "fintoc",
                "returnUrl": "https://app.vitrinadev.com/w/agencia/settings/billing/invoices?fintoc=return",
                "email": "facturacion@automotora-demo.cl",
                "rut": "76.123.456-7"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enrollment started (Fintoc → setupUrl) or completed (MercadoPago → active)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentMethodSetup"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "fintoc",
                    "status": "pending",
                    "mandateId": "f1f1f1f1-0000-4000-8000-000000000002",
                    "setupUrl": "https://pay.fintoc.com/subscriptions/setup/example"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sale-notes": {
      "get": {
        "summary": "List notas de venta",
        "description": "The dealership’s sale documents, newest first by `issued_at`. **Voided documents are INCLUDED by default** — a voided nota de venta keeps its folio and stays listable so \"where is nota de venta #47?\" has an answer (ADR 0058 §10). Pass `exclude_voided=true` to see only live ones; only the literal string `true` opts in, so `exclude_voided=false` does not hide them.\n\n**`lead_id` answers \"which nota de venta closed this deal?\"** (ERP S15-16) — a lead UUID or its folio (`L-89`); a foreign or unknown lead is a 404. It does NOT imply `exclude_voided`, deliberately: a voided document must stay reachable from the deal it closed, or the void becomes the one event that erases the answer. So a deal that was closed, voided and re-issued returns both documents here, and the live one is the row whose `status` is `issued`.\n\n**Every row carries `totals`** — the SAME object `GET /sale-notes/{id}` embeds, computed by the same roll-up: `total_a_pagar_clp` (price + IVA + Σ statutory add-ons), `total_pagos_clp`, `saldo_clp`, `payment_count`, `complete` and `gaps`. ⚠ **A POSITIVE `saldo_clp` means the customer has OVERPAID; a NEGATIVE one is what they still owe** (BR-245 — it is the sign the printed document uses). `total_a_pagar_clp` and `saldo_clp` are both **`null`** when `tax_clp` was never recorded, with `gaps` naming it: that is \"not computable\", never zero, and a client must render it as such rather than treating the unrecorded IVA as 0. Voided rows are totalled like any other — the money against them was really taken, and `status` is what says the document is dead. Do not sum the payments client-side; there is one definition of this balance and this is it.\n\n**`economics` — only for callers holding `dealership_economics:read`** (absent, not null, for everyone else): `margen_bruto_clp` (Margen bruto), `margen_neto_clp` (Utilidad del negocio), `comisiones_vendedores_clp` (Comisión) and `canal` (where the buyer came from), each the SAME figure `GET /margin` serves — one definition, computed in batch for the page. A figure that is not computable is `null`, never zero, and `gaps` names the missing input (`cost_rollup` = Sin costo, `seller_commission` = Sin comisión, `liquidacion` = Sin liquidación). A voided row has `economics: null`.\n\n**`status`** narrows to one derived status; `status=issued` is the «Por aprobar» list (emitted, waiting for its approval — ADR 0114 §4).\n\nUnknown query parameters are a **400**, not a silent drop: a filter the caller believes is being applied, returning a full unfiltered list with a 200, is a worse answer than a refusal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "exclude_voided",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^L-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "issued",
                "approved",
                "voided"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Notas de venta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "V-118",
                      "display_seq": 118,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                      "lead_id": null,
                      "converted_from_reservation_id": null,
                      "seller_of_record": "automotora",
                      "salesperson_id": "11111111-0000-4000-8000-000000000001",
                      "net_clp": 11900000,
                      "list_price_clp": 12490000,
                      "discount_clp": 590000,
                      "tax_clp": 1900000,
                      "tax_treatment": "afecto",
                      "status": "issued",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "emitted_by": "11111111-0000-4000-8000-000000000001",
                      "approved_by": null,
                      "approved_at": null,
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "created_at": "2026-09-19T13:00:00.000Z",
                      "updated_at": "2026-09-19T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Issue a nota de venta",
        "description": "Issues the document and allocates its folio (`V-1`, `V-2`, …) inside the inserting transaction, so a failed issue RELEASES the number rather than burning it out of a legally accountable series.\n\n**`emitted_by` is the caller** (ADR 0114 §4): the member (also behind a personal token) or the API key that issued it, stamped from the credential — there is no body field for it.\n\n**Money is whole CLP pesos** (`bigint`, never minor units, never decimals) and the triple is `net_clp` + `tax_clp` + `tax_treatment`. Omit `tax_clp` to record \"not recorded\"; pass `0` to record \"no tax\" — ADR 0058 §2 makes those two different claims and nothing collapses them. `tax_treatment` is REQUIRED and has no default: the same peso falls inside or outside the IVA base depending on how the deal is structured, so a default would be a guess stored as a determination.\n\n`vehicle_id`, `buyer_contact_id` and `lead_id` must belong to the calling workspace — no foreign key carries a tenant, so this is checked before the insert. A foreign id and a nonexistent one answer identically (404), deliberately: a difference between them is an existence oracle. A soft-deleted or merged vehicle is also refused.\n\n**`lead_id` names the deal this document CLOSES** (ERP S15-16) — a lead UUID or its folio (`L-89`). Omit it for a walk-in sale with no CRM deal; absent means \"no originating deal\", never \"unknown\". The link is then queryable both ways: `GET /sale-notes?lead_id=…` from the deal, and the `lead_id` field on the document itself. A deal has **at most one live closing document** — a second one is a **409** until the first is voided, enforced by a partial unique index rather than by a pre-read, so two concurrent issues cannot both win. The link is set here and never edited: there is no update endpoint, and the correction path is the one ADR 0058 §10 already defines — void (the row, its folio and its link all survive) and issue a replacement.\n\n**Every sale has exactly one Vendedor** (ADR 0114 §3) — the team member who closed it: the `closer` participation, echoed on `salesperson_id`. When the request names nobody, the Vendedor defaults to the assignee of the `lead_id` deal, else the car's Responsable del auto (`vehicle.responsible_user_id`), skipping anyone who is no longer an active member. When neither resolves, the emit is a **400** with `field_errors.salesperson_id` and **no folio consumed**. The default is a proposal: naming someone else always wins.\n\n`approved_by` and `approved_at` are all-or-nothing. Timestamps accept either UTC (`…Z`) or an explicit offset (`…-04:00`, Chile). Unknown body keys are a **400** — `tax` is one character from `tax_clp`, and silently discarding it would record \"no IVA was entered\" for a caller who entered it. Answers 201.\n\n**`funding` IS THE BALANCE GATE (ERP S9-5, BR-418)** — where the money for this sale comes from. Its payments are validated BEFORE anything is written (a malformed row is a **400** with **no folio consumed**) and written into the SAME transaction as the document — so a payment the database refuses rolls `V-n` back rather than burning it on a half-recorded sale. `price + Σ statutory add-ons` is weighed against `Σ these payments + retoma + credit`: **a plan that comes up short still issues** (201) — the usual case of a sale issued with only the deposit — and the note reads `funding.standing: \"short\"` with `funding.gap_clp` on `GET /sale-notes/{id}`; record the rest later with `POST /document-payments`. **Over-funding is allowed** and comes back labelled as `saldo_a_favor_del_cliente_clp`; it is never an error and is never silently clamped. A plan stated against a document with no `tax_clp` is a **400** (the balance cannot be computed: an unrecorded IVA is not zero).\n\nThere is deliberately **no `retoma_clp` and no `credit_clp`** inside `funding`: a hand-typed figure for either would be an unbacked claim the gate then counts as money. **Omitting `funding` is not a bypass** — the document issues as it always has and reads `short` on `GET /sale-notes/{id}` with the gap stated. Use `POST /sale-notes/funding/preview` to see the gap before committing to the issue.\n\n**`trade_ins` IS LA RETOMA (ERP S9-6, BR-451–460)** — the cars the buyer is handing over in part-exchange. ⚠ **A trade-in is a PAYMENT and never a price reduction** (BR-455): nothing here touches `net_clp` or any price field, and `Σ precio_toma_clp` counts on the FUNDING side of the balance gate beside the payments. *\"If a trade-in reduced the price, any seller could inflate the take-in to disguise a discount and reported revenue would collapse.\"*\n\nEach car must ALREADY EXIST as a vehicle in this workspace (BR-451 — the car is about to enter stock and needs identity before it can carry a cost), must not be the car being sold (**BR-452**), and must not already have an acquisition record — a unit the dealership once acquired cannot come back as a trade-in yet, and that is a **409** with the rule in it. `precio_toma_clp` is the NEGOTIATED credit, whole CLP pesos and STRICTLY positive (BR-453): a business decision, not the car’s list price and not its SII appraisal.\n\n**Issuing with `trade_ins` also records HOW THE INCOMING UNIT ARRIVED** (BR-458): one `vehicle_acquisition` of kind `retoma` per car, naming this document, whose cost basis is the take-in value and whose `tenencia` the database derives as `propio` — written in the SAME transaction that allocated the folio, so every refusal leaves nothing behind and consumes no `V-n`. The document must name its `buyer_contact_id`: the person trading the car in is the party the dealership bought it from, and BR-460 makes that chain the only thing that identifies the legal seller when the car is sold on.\n\n**`credit` IS THE BROKERED FINANCING (ERP S9-7, BR-461–464)** — the financiera and the amount it is lending. ⚠ **It counts in the balance gate BEFORE the cash lands** (BR-463: *\"the sale is funded even though the cash is not in\"*), which is what makes a FINANCED deal balance at the counter. ⚠ **It is not a payment and has no `instrument`**: `credito` is deliberately absent from the `customer_payment` instrument vocabulary (BR-264/BR-450/BR-1010), so it is BR-418’s own term and never a payment row. `institution` is the credit’s identity on the document (BR-464) and is required; `application_number` is internal. There is no vehicle and no client field — BR-461’s pair is this note’s, already fixed.\n\nOmitting `credit` is the **ordinary case even for a financed deal**: the dealership records financing as part of closing, after the fact (112 credits taken against 0 sent in the recovered system), through `PUT /sale-notes/{id}/credit`. The note reads `short` in between, with the gap stated.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "buyer_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "lead_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^L-\\d+$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "seller_of_record": {
                    "type": "string",
                    "enum": [
                      "consignante",
                      "retoma",
                      "automotora"
                    ]
                  },
                  "salesperson_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "participations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "user_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "role": {
                          "type": "string",
                          "enum": [
                            "closer",
                            "collaborator"
                          ]
                        },
                        "commission_rate_bp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 10000
                        },
                        "commission_basis_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 999999999999
                        }
                      },
                      "required": [
                        "user_id",
                        "role"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 10
                  },
                  "net_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado",
                      "pass_through"
                    ]
                  },
                  "expected_list_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "issued_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "approved_by": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "approved_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "funding": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "payments": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "instrument": {
                              "type": "string",
                              "enum": [
                                "efectivo",
                                "transferencia",
                                "cheque",
                                "vale_vista",
                                "tarjeta",
                                "tarjeta_debito",
                                "tarjeta_credito",
                                "otro"
                              ]
                            },
                            "amount_clp": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 999999999999
                            },
                            "card_surcharge_clp": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 0,
                              "maximum": 999999999999
                            },
                            "paid_on": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "bank": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 200
                            },
                            "account_number": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 100
                            },
                            "document_number": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 100
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 2000
                            }
                          },
                          "required": [
                            "instrument",
                            "amount_clp",
                            "paid_on"
                          ],
                          "additionalProperties": false
                        },
                        "maxItems": 50
                      }
                    },
                    "required": [
                      "payments"
                    ],
                    "additionalProperties": false
                  },
                  "trade_ins": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "vehicle_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "precio_toma_clp": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999999999999
                        }
                      },
                      "required": [
                        "vehicle_id",
                        "precio_toma_clp"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 10
                  },
                  "credit": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "institution": {
                        "type": "string",
                        "maxLength": 200
                      },
                      "amount_clp": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 999999999999
                      },
                      "application_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 100
                      },
                      "credit_application_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid"
                      }
                    },
                    "required": [
                      "institution",
                      "amount_clp"
                    ],
                    "additionalProperties": false
                  },
                  "carry_reservation_abono": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "vehicle_id",
                  "net_clp",
                  "tax_treatment"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                "seller_of_record": "automotora",
                "salesperson_id": "11111111-0000-4000-8000-000000000001",
                "net_clp": 11900000,
                "tax_clp": 1900000,
                "tax_treatment": "afecto"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The issued nota de venta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "V-118",
                    "display_seq": 118,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "lead_id": null,
                    "converted_from_reservation_id": null,
                    "seller_of_record": "automotora",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "net_clp": 11900000,
                    "list_price_clp": 12490000,
                    "discount_clp": 590000,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "issued",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "emitted_by": "11111111-0000-4000-8000-000000000001",
                    "approved_by": null,
                    "approved_at": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-19T13:00:00.000Z",
                    "updated_at": "2026-09-19T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sale-notes/{id}": {
      "get": {
        "summary": "Fetch one nota de venta",
        "description": "Accepts the UUID **or the folio** (`V-47`) — the folio is the form a dealer actually has in hand. Only the `V-` series resolves here; another entity’s display id (`T-1247`) is not a nota de venta.\n\nA document belonging to another workspace is a 404, indistinguishable from one that does not exist.\n\n**It answers the money too.** `payments` is every amount received against this note with the instrument it arrived as; `totals` carries `total_a_pagar_clp` / `total_pagos_clp` / `saldo_clp` (a POSITIVE saldo is an over-payment, a NEGATIVE one is what the customer still owes); and since ERP S9-5 `funding` carries **BR-418’s verdict** — `standing` is `funded`, `overfunded` (with `saldo_a_favor_del_cliente_clp`), `short` (with `gap_clp`) or `not_computable`, plus the five `terms` it was reached from. `total_a_pagar_clp` INCLUDES the statutory add-ons charged beside the price (BR-243).\n\nNothing here is stored: the standing is DERIVED from the rows on every read, there is no `funding_standing` column, and there is nothing to write. A document issued without a funding plan reads `short`, which is the honest state and not an error.\n\n**`timeline` says who did what, by name** (ADR 0114 §4): one entry per step that happened, in the order the document lives them — `emitted` (`issued_at`, `emitted_by`), `approved`, `printed` (the compraventa in force) and `voided` — each with its instant and its `actor` (`{ id, name, kind }`, `kind` = `member` or `api_key`). `actor` is `null` when the document does not record who did it (a note emitted before `emitted_by` existed); `actor.name` is `null` when that person has no name on file. Show either in words — never as an anonymous teammate. Approval is a separate step under `sale_notes:write`, so the emitter and the approver are often different people.\n\n**`receivable_obligation_id`** is the open ledger receivable this note settles — pass it as `obligation_id` to `POST /payments` to apply a payment to this note. `null` once the note is voided (the void releases its money) or when it has no receivable.\n\n**`print_missing_fields`** is what `GET /sale-notes/{id}/pdf` would refuse with right now — the SAME `{ variable, label, fix, contact_id? }` list as its 409 `details.missing_fields` — so a screen can ask for the buyer’s domicilio while the buyer is still at the counter. `[]` when the note prints, and always `[]` on a voided note.\n\n**`trade_ins` names the cars taken in part-exchange** (ERP S9-6) — one entry per car with the line’s own id (never the vehicle’s: BR-457), the `vehicle_id`, the `precio_toma_clp` allowed for it, and the `acquisition_id` of the `vehicle_acquisition` that arrival created. That is the SALE → UNIT direction; the UNIT → SALE direction needs no surface here, because `vehicle_acquisition.sale_note_id` is projected on every read of `GET /vehicle-acquisitions?vehicle_id=`. `acquisition_id` is `null` once the document has been VOIDED — a void retracts the arrival and keeps the line, so the null says exactly what happened: this credit was agreed, and the arrival it caused has been withdrawn.\n\n**`credit` is the LIVE brokered credit, or `null`** (ERP S9-7) — the `institution` that is lending (BR-464 makes the financiera the credit’s identity on the document), the `amount_clp`, and its OWN `state`: `committed` (the bank has promised) or `disbursed` (the money has arrived, with `disbursed_at` / `disbursed_by` beside it). ⚠ **Both count the same in `funding`** (BR-463), so never read `funded` as \"the cash is in\" — read `credit.state` for that. The committed-vs-collected gap is exactly this pair of fields read side by side. ⚠ **The credit’s state is INDEPENDENT of the document’s `status`**: a voided nota de venta keeps its credit row untouched, and a disbursed credit does not make the document anything. Credits that were REPLACED or REMOVED are tombstoned and are deliberately not served here — they are history, not funding, and listing them beside the live one would invite a client to add them up.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The nota de venta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "V-118",
                    "display_seq": 118,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "lead_id": null,
                    "converted_from_reservation_id": null,
                    "seller_of_record": "automotora",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "net_clp": 11900000,
                    "list_price_clp": 12490000,
                    "discount_clp": 590000,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "issued",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "emitted_by": "11111111-0000-4000-8000-000000000001",
                    "approved_by": null,
                    "approved_at": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-19T13:00:00.000Z",
                    "updated_at": "2026-09-19T13:00:00.000Z",
                    "receivable_obligation_id": "01a1c1df-a6ea-779f-9310-bdbb2eadd184",
                    "print_missing_fields": [
                      {
                        "variable": "COMPRADOR_DOMICILIO",
                        "label": "Domicilio del comprador",
                        "fix": "buyer_contact",
                        "contact_id": "01a1ad2b-a52a-7b04-abf8-389e20e8217a"
                      }
                    ],
                    "payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": null,
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "carried_from_reservation_id": null,
                        "instrument": "transferencia",
                        "amount_clp": 13800000,
                        "paid_on": "2026-09-19",
                        "bank": "Banco de Chile",
                        "account_number": null,
                        "document_number": null,
                        "note": null,
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "created_at": "2026-09-19T13:05:00.000Z"
                      }
                    ],
                    "totals": {
                      "total_a_pagar_clp": 13800000,
                      "total_pagos_clp": 13800000,
                      "saldo_clp": 0,
                      "recargo_tarjeta_clp": 0,
                      "total_a_cobrar_clp": 13800000,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "funding": {
                      "standing": "funded",
                      "balanced": true,
                      "terms": {
                        "price_clp": 13800000,
                        "add_on_charges_clp": 0,
                        "payments_clp": 13800000,
                        "retoma_clp": 0,
                        "credit_clp": 0
                      },
                      "total_a_pagar_clp": 13800000,
                      "total_consideration_clp": 13800000,
                      "saldo_clp": 0,
                      "gap_clp": null,
                      "saldo_a_favor_del_cliente_clp": null,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "trade_ins": [],
                    "credit": null,
                    "timeline": [
                      {
                        "step": "emitted",
                        "at": "2026-09-19T13:00:00.000Z",
                        "actor": {
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": "Martina Reyes",
                          "kind": "member"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/buyer": {
      "patch": {
        "summary": "Assign or change the buyer of a nota de venta",
        "description": "Sets `buyer_contact_id` on a live (issued or approved) nota de venta — the fix for a note issued without its comprador, without voiding it. The contact must belong to this workspace (404 otherwise, or when it was merged away). Responds with the same read as `GET /sale-notes/{id}`. Setting the buyer it already has is a no-op 200.\n\n**409** with `details.blocked_reason` and a Spanish message when the buyer is already written into something else: `sale_note_voided`, `compraventa_issued` (the contract names the buyer), `trade_ins_recorded` (a retoma came from the current buyer) or `credit_attached` (the credit’s borrower is the current buyer). The way out for those is void + re-issue. Requires `sale_notes:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "buyer_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "buyer_contact_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "buyer_contact_id": "22222222-0000-4000-8000-000000000002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The nota de venta, as GET /sale-notes/{id} reads it",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "V-118",
                    "display_seq": 118,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "lead_id": null,
                    "converted_from_reservation_id": null,
                    "seller_of_record": "automotora",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "net_clp": 11900000,
                    "list_price_clp": 12490000,
                    "discount_clp": 590000,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "issued",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "emitted_by": "11111111-0000-4000-8000-000000000001",
                    "approved_by": null,
                    "approved_at": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-19T13:00:00.000Z",
                    "updated_at": "2026-09-19T13:00:00.000Z",
                    "receivable_obligation_id": "01a1c1df-a6ea-779f-9310-bdbb2eadd184",
                    "print_missing_fields": [
                      {
                        "variable": "COMPRADOR_DOMICILIO",
                        "label": "Domicilio del comprador",
                        "fix": "buyer_contact",
                        "contact_id": "01a1ad2b-a52a-7b04-abf8-389e20e8217a"
                      }
                    ],
                    "payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": null,
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "carried_from_reservation_id": null,
                        "instrument": "transferencia",
                        "amount_clp": 13800000,
                        "paid_on": "2026-09-19",
                        "bank": "Banco de Chile",
                        "account_number": null,
                        "document_number": null,
                        "note": null,
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "created_at": "2026-09-19T13:05:00.000Z"
                      }
                    ],
                    "totals": {
                      "total_a_pagar_clp": 13800000,
                      "total_pagos_clp": 13800000,
                      "saldo_clp": 0,
                      "recargo_tarjeta_clp": 0,
                      "total_a_cobrar_clp": 13800000,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "funding": {
                      "standing": "funded",
                      "balanced": true,
                      "terms": {
                        "price_clp": 13800000,
                        "add_on_charges_clp": 0,
                        "payments_clp": 13800000,
                        "retoma_clp": 0,
                        "credit_clp": 0
                      },
                      "total_a_pagar_clp": 13800000,
                      "total_consideration_clp": 13800000,
                      "saldo_clp": 0,
                      "gap_clp": null,
                      "saldo_a_favor_del_cliente_clp": null,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "trade_ins": [],
                    "credit": null,
                    "timeline": [
                      {
                        "step": "emitted",
                        "at": "2026-09-19T13:00:00.000Z",
                        "actor": {
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": "Martina Reyes",
                          "kind": "member"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/pdf": {
      "get": {
        "summary": "Print the nota de venta",
        "description": "The nota de venta as a printable PDF (`application/pdf`, served `inline`): the workspace’s default `sale_note` template — or `template_id` — filled with this sale’s facts (dealership legal identity, folio, ejecutivo, buyer, seller, vehicle, price, payments). With no live template it prints the standard text. Rendered on demand and not stored (the frozen legal instrument is the compraventa). Any status prints; a voided note carries an «ANULADA» banner. The `X-Template-Version` header names the prose used.\n\n**409** when a fact the template needs is missing — nothing is printed with a blank. `details.blocked_reason` is `sale_note_print_incomplete` (or the legal-seller code when the seller is what is missing), `details.missing` lists Spanish labels (e.g. «RUT de la automotora», «RUT del comprador») and `details.missing_fields[]` carries `{ variable, label, fix, contact_id? }` where `fix` is `organization_legal` | `buyer_contact` | `owner_contact` | `sale_note` | `legal_seller` — the form to open. Requires `sale_notes:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "template_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The printable nota de venta",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A fact the template needs is missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "No se puede imprimir la nota de venta V-1: faltan datos. Falta: Razón social de la automotora, RUT de la automotora, RUT del comprador.",
                    "details": {
                      "blocked_reason": "sale_note_print_incomplete",
                      "sale_note_id": "7c1e9a52-3b4d-4f6a-9e21-5d8b0c4f2a17",
                      "missing": [
                        "Razón social de la automotora",
                        "RUT de la automotora",
                        "RUT del comprador"
                      ],
                      "missing_fields": [
                        {
                          "variable": "MANDATARIA_RAZON_SOCIAL",
                          "label": "Razón social de la automotora",
                          "fix": "organization_legal"
                        },
                        {
                          "variable": "MANDATARIA_RUT",
                          "label": "RUT de la automotora",
                          "fix": "organization_legal"
                        },
                        {
                          "variable": "COMPRADOR_RUT",
                          "label": "RUT del comprador",
                          "fix": "buyer_contact",
                          "contact_id": "2f6d8b14-9c3a-4e57-a0b2-6e1f7d3c9a48"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/approve": {
      "post": {
        "summary": "Approve a nota de venta (the sale fan-out)",
        "description": "Approves the document — the instant it becomes **legally valid** (BR-485) and the instant every consequence of the sale fires. All of it happens in **ONE database transaction**, so either all of it happened or none of it did:\n\n* the document is stamped with WHO approved it and WHEN (BR-484);\n* **the car is marked `vendido`** (BR-486), which is TERMINAL — a sold unit never sells or lists again (ADR 0030, regla de Estadía);\n* every **vigente cotización** on that unit is voided in the same write (BR-215), so the car is never left sold with a live price commitment standing on it;\n* the **Negocio** the note names (`lead_id`) is closed WON (BR-487), by a real stage move rather than a status patch.\n\nAfterwards the document appears in `GET /sale-notes/post-sale-obligations` (BR-489) and its Contrato de Compraventa and Declaración de Responsabilidad become printable (BR-488). The unit’s portal adverts are retracted immediately AFTER the transaction commits — never before, because a queue job cannot be rolled back and a rolled-back approval must not unpublish a car that was not sold.\n\n**The body is empty and strict.** The approver is the authenticated principal and the instant is server-stamped; a body naming either is a 400. Back-dating an approval is expressible only on `POST /sale-notes` (`approved_at`), and only for the ADR 0058 §11 talonario cutover.\n\n**Refusals write NOTHING** — not the stamp, not the sold flag, not the quote deaths, not the deal close, and no advert job:\n\n* **409** when the sale’s price is below the level this organisation requires an authorisation for and no APPROVED `price_approval` names this document (BR-433/BR-434 — a salesperson cannot authorise their own discount). Raise one with `POST /price-approvals` and have somebody else decide it. A caller holding `price_approval:approve` (an owner or admin) is not refused: approving the sale is their authorisation of the price. The refusal deliberately states no figure: the shortfall and the reference are cost disclosures gated on `dealership_economics:read`.\n* **409** when the unit has already been sold by another document.\n* **409** when the car is consigned (active contrato de consignación) and its mandato was never emitted — «Emite el mandato antes de aprobar» (`details.reason: mandate_not_emitted`). Emit it with `POST /consignments/{id}/contract`.\n* **409** when the document is already approved, or is voided — an approval names a person and a moment and cannot be restated.\n* **400** when the deal’s board has no Won column, or forbids the move onto it.\n\nTwo simultaneous approvals resolve to exactly one 200 and one 409, and the fan-out runs once.\n\nRequires `sale_notes:write` — approving is the completion of the same act as issuing, not a separate authority (unlike `sale_notes:void`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The approved nota de venta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "V-118",
                    "display_seq": 118,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "lead_id": null,
                    "converted_from_reservation_id": null,
                    "seller_of_record": "automotora",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "net_clp": 11900000,
                    "list_price_clp": 12490000,
                    "discount_clp": 590000,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "approved",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "emitted_by": "11111111-0000-4000-8000-000000000001",
                    "approved_by": "11111111-0000-4000-8000-000000000001",
                    "approved_at": "2026-09-19T14:10:00.000Z",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-19T13:00:00.000Z",
                    "updated_at": "2026-09-19T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/vendedor-default": {
      "get": {
        "summary": "The Vendedor a new nota de venta would get",
        "description": "Who `POST /sale-notes` credits as the **Vendedor** when its body names nobody (ADR 0114 §3), so a screen or an agent can propose that person up front: the assignee of the sale’s lead — the `lead_id` given, else the buyer’s lead on this car (`buyer_contact_id` + `vehicle_id`) — otherwise the car’s **Responsable del auto**. A member who left the workspace is skipped. `source` says which rung answered (`lead_owner` | `vehicle_responsible`) and `lead` (`id`, `display_id`) names the lead when it did. `data` is `null` when nobody can be proposed: the emit is then refused until a Vendedor is chosen. The inferred lead only proposes the Vendedor; it is never written as the nota de venta’s `lead_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "buyer_contact_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^L-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The proposed Vendedor, or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "user_id": "11111111-0000-4000-8000-000000000002",
                    "source": "lead_owner",
                    "lead": {
                      "id": "99999999-0000-4000-8000-000000000001",
                      "display_id": "L-47"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/void": {
      "post": {
        "summary": "Void a nota de venta",
        "description": "Voids the document. It is **never deleted and it KEEPS ITS FOLIO** (ADR 0058 §10): the row records the time, the reason and the acting principal and remains listable. There is deliberately no `DELETE` verb on this resource.\n\n`void_reason` is mandatory and must contain VISIBLE text — a reason made only of whitespace, a U+00A0 or a zero-width space is refused at the edge and by a table CHECK, because a reason nobody can see is not a reason.\n\nThe actor comes from the authenticated principal; a `voided_by` in the body is IGNORED rather than rejected — a self-declared actor is not an attestation. Voiding twice is a **409**: the first actor and reason stand.\n\nRequires `sale_notes:void`, which is a DIFFERENT authority from `sale_notes:write` — holding the write scope does not permit a void.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "void_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "void_reason"
                ]
              },
              "example": {
                "void_reason": "el banco rechazó el financiamiento del comprador"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The voided nota de venta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "V-118",
                    "display_seq": 118,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "lead_id": null,
                    "converted_from_reservation_id": null,
                    "seller_of_record": "automotora",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "net_clp": 11900000,
                    "list_price_clp": 12490000,
                    "discount_clp": 590000,
                    "tax_clp": 1900000,
                    "tax_treatment": "afecto",
                    "status": "voided",
                    "issued_at": "2026-09-19T13:00:00.000Z",
                    "emitted_by": "11111111-0000-4000-8000-000000000001",
                    "approved_by": null,
                    "approved_at": null,
                    "voided_at": "2026-09-20T09:00:00.000Z",
                    "void_reason": "el banco rechazó el financiamiento del comprador",
                    "voided_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-19T13:00:00.000Z",
                    "updated_at": "2026-09-19T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/quotes": {
      "get": {
        "summary": "List cotizaciones",
        "description": "The quote register, most recently issued first.\n\n**Everything each row says about the CAR is the frozen snapshot taken at issuance**, not what the stock record says today — this endpoint never joins the vehicle.\n\nThe default page is the **working register**: archived documents are filed away and do not appear. Pass `include_archived=true` to see them (only the literal string `true` opts in).\n\n**VOIDED quotes ARE listed, with their folios.** A voided cotización is never hidden, so \"where is Q-47?\" always has an answer (ADR 0058 §10) — there is deliberately no `exclude_voided` parameter. Each row carries a derived `state`: `vigente` | `expirada` | `nula`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "include_archived",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The cotizaciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d2d2d2d2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "Q-47",
                      "display_seq": 47,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                      "salesperson_id": "11111111-0000-4000-8000-000000000001",
                      "list_price_clp": 12500000,
                      "offered_price_clp": 11900000,
                      "tax_treatment": "afecto",
                      "discount_pct": 4.8,
                      "unit_make": "Toyota",
                      "unit_model": "Corolla",
                      "unit_version": "XEI 2.0 CVT",
                      "unit_year": 2023,
                      "unit_plate": "RJKL48",
                      "unit_vin": "MMSDA31S6M0123457",
                      "unit_odometer_value": 18500,
                      "unit_odometer_unit": "km",
                      "unit_color": "Gris plata",
                      "discount_hidden": false,
                      "issued_at": "2026-09-08T14:02:11.000Z",
                      "expires_on": "2026-09-23",
                      "state": "vigente",
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "extended_at": null,
                      "extend_reason": null,
                      "extended_by": null,
                      "archived_at": null,
                      "archive_reason": null,
                      "archived_by": null,
                      "created_at": "2026-09-08T14:02:11.000Z",
                      "updated_at": "2026-09-08T14:02:11.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Issue a cotización",
        "description": "Issues a quote and allocates its folio (`Q-1`, `Q-2`, …) inside the inserting transaction, so a failed issue RELEASES the number rather than burning it.\n\n**A cotización is a SNAPSHOT, not a view.** The vehicle’s asking price and its identity — marca, modelo, versión, año, patente, VIN, kilometraje, color — are copied onto the document by the database at this moment and are never re-read afterwards, so **repricing or correcting the car does not change any quote already issued**. Those fields are therefore NOT request parameters: a caller cannot record that a unit was listed at a price it never carried. Unknown body keys are a **400** for exactly that reason.\n\n`offered_price_clp` is whole CLP pesos (`bigint`, never minor units, never decimals). The **discount % is derived**, never stored and never accepted as input — `100 × (list − offered) / list`, clamped at 0 — which is safe precisely because both of its inputs are frozen on the row. `tax_treatment` is REQUIRED and has no default; it is not inherited from the unit’s régimen IVA, which can only say `afecto`/`exento` and cannot express `pass_through`.\n\n`buyer_contact_id` is **required** — a price offer that names no offeree is not an offer. It and `vehicle_id` must belong to the calling workspace; a foreign id and a nonexistent one answer identically (404), deliberately, because a difference between them is an existence oracle. A soft-deleted or merged vehicle is also refused.\n\nThe offer stands **through the whole of** `expires_on` (default: 15 days from the issue date) and lapses the day after. A quote is explicitly **not a reservation** — it creates no hold on the unit, and several customers may hold quotes on the same car. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "buyer_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "salesperson_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "offered_price_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado",
                      "pass_through"
                    ]
                  },
                  "discount_hidden": {
                    "type": "boolean"
                  },
                  "issued_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "expires_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "vehicle_id",
                  "buyer_contact_id",
                  "offered_price_clp",
                  "tax_treatment"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                "salesperson_id": "11111111-0000-4000-8000-000000000001",
                "offered_price_clp": 11900000,
                "tax_treatment": "afecto",
                "expires_on": "2026-09-23"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The issued cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "Q-47",
                    "display_seq": 47,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "list_price_clp": 12500000,
                    "offered_price_clp": 11900000,
                    "tax_treatment": "afecto",
                    "discount_pct": 4.8,
                    "unit_make": "Toyota",
                    "unit_model": "Corolla",
                    "unit_version": "XEI 2.0 CVT",
                    "unit_year": 2023,
                    "unit_plate": "RJKL48",
                    "unit_vin": "MMSDA31S6M0123457",
                    "unit_odometer_value": 18500,
                    "unit_odometer_unit": "km",
                    "unit_color": "Gris plata",
                    "discount_hidden": false,
                    "issued_at": "2026-09-08T14:02:11.000Z",
                    "expires_on": "2026-09-23",
                    "state": "vigente",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "extended_at": null,
                    "extend_reason": null,
                    "extended_by": null,
                    "archived_at": null,
                    "archive_reason": null,
                    "archived_by": null,
                    "created_at": "2026-09-08T14:02:11.000Z",
                    "updated_at": "2026-09-08T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/quotes/{id}": {
      "get": {
        "summary": "Fetch one cotización",
        "description": "Accepts the UUID **or the folio** (`Q-47`) — the folio is the form the customer is holding. Only the `Q-` series resolves here; another entity’s display id (`T-1247`) is not a quote.\n\nEverything returned about the CAR is the frozen snapshot as at issuance, not the stock record’s current values. `state` is DERIVED from two frozen columns plus the clock and is one of `vigente` | `expirada` | `nula`: a quote is still `vigente` throughout its `expires_on` day, and a void outranks an expiry (a voided quote reads `nula` even after its date has passed). There is no stored status column, so the state can never drift from the facts it comes from.\n\nA quote belonging to another workspace is a 404, indistinguishable from one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^Q-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "Q-47",
                    "display_seq": 47,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "list_price_clp": 12500000,
                    "offered_price_clp": 11900000,
                    "tax_treatment": "afecto",
                    "discount_pct": 4.8,
                    "unit_make": "Toyota",
                    "unit_model": "Corolla",
                    "unit_version": "XEI 2.0 CVT",
                    "unit_year": 2023,
                    "unit_plate": "RJKL48",
                    "unit_vin": "MMSDA31S6M0123457",
                    "unit_odometer_value": 18500,
                    "unit_odometer_unit": "km",
                    "unit_color": "Gris plata",
                    "discount_hidden": false,
                    "issued_at": "2026-09-08T14:02:11.000Z",
                    "expires_on": "2026-09-23",
                    "state": "vigente",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "extended_at": null,
                    "extend_reason": null,
                    "extended_by": null,
                    "archived_at": null,
                    "archive_reason": null,
                    "archived_by": null,
                    "created_at": "2026-09-08T14:02:11.000Z",
                    "updated_at": "2026-09-08T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/quotes/{id}/void": {
      "post": {
        "summary": "Void a cotización",
        "description": "Voids the quote. It is **never deleted and it KEEPS ITS FOLIO** (ADR 0058 §10): the row records the time, the reason and the acting principal and remains readable. There is deliberately no `DELETE` verb on this resource — and no `PUT`/`PATCH` either, because the issued document is immutable. **The correction path for anything the document says is void + re-issue**, which is also the only path that leaves a trail.\n\nThe void is **terminal**: a voided quote can never be restored, extended or re-voided, so a second attempt is a **409** and the first actor and reason stand. An EXPIRED quote can still be voided.\n\n`void_reason` is mandatory and must contain VISIBLE text — a reason made only of whitespace, a U+00A0 or a zero-width space is refused at the edge and by a table CHECK, because \"why did we cancel 40 quotes this month\" is answerable only if the field is populated.\n\nThe actor comes from the authenticated principal; a `voided_by` in the body is IGNORED rather than rejected — a self-declared actor is not an attestation. Requires `quotes:void`, a DIFFERENT authority from `quotes:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^Q-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "void_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "void_reason"
                ]
              },
              "example": {
                "void_reason": "el cliente desistió de la compra"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The voided cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "Q-52",
                    "display_seq": 52,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "list_price_clp": 12500000,
                    "offered_price_clp": 11900000,
                    "tax_treatment": "afecto",
                    "discount_pct": 4.8,
                    "unit_make": "Toyota",
                    "unit_model": "Corolla",
                    "unit_version": null,
                    "unit_year": 2023,
                    "unit_plate": null,
                    "unit_vin": null,
                    "unit_odometer_value": 18500,
                    "unit_odometer_unit": "km",
                    "unit_color": "Gris plata",
                    "discount_hidden": false,
                    "issued_at": "2026-09-08T14:02:11.000Z",
                    "expires_on": "2026-09-23",
                    "state": "nula",
                    "voided_at": "2026-09-15T11:20:03.000Z",
                    "void_reason": "el cliente desistió de la compra",
                    "voided_by": "11111111-0000-4000-8000-000000000001",
                    "extended_at": null,
                    "extend_reason": null,
                    "extended_by": null,
                    "archived_at": null,
                    "archive_reason": null,
                    "archived_by": null,
                    "created_at": "2026-09-08T14:02:11.000Z",
                    "updated_at": "2026-09-08T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/quotes/{id}/extend": {
      "post": {
        "summary": "Extend a cotización",
        "description": "Moves the validity window to a new `expires_on`, **recording who did it and why**. This is the ONLY change an issued quote accepts (BR-221): the price, the car, the buyer and everything else the document says are frozen.\n\nPermitted on a `vigente` quote **and on an `expirada` one** — extending an expired quote returns it to `vigente`, which is the capability's whole purpose (the customer's financing took longer than the 15-day window). **Refused on a `nula` one with a 409**: the void is terminal (BR-222), and the table's own trigger refuses it too.\n\n`extend_reason` is MANDATORY and must contain VISIBLE text. Extending is economically identical to re-issuing, so it is attributable on the same terms — un-attributed, it would be the one way to move a price commitment with no name on it (BR-224). The database enforces this: `expires_on` may only move in a statement that also stamps a fresh `extended_at` / `extend_reason` / `extended_by`, so an unattributed extension is unstorable rather than merely discouraged.\n\nThe new date may be any real calendar day, in either direction and with no bound (BR-214 as recovered) — but it must DIFFER from the date the document already carries, since an extension that moves nothing would record an act that never happened (**400**).\n\nThe actor comes from the authenticated principal. Unlike `/void`, this body is STRICT: a body-supplied `extended_by` is a **400**, not a silently ignored key. The row carries the LAST extension; every extension also writes a `quotes.extend` audit row, which is the full history. Requires `quotes:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^Q-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expires_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "extend_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "expires_on",
                  "extend_reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "expires_on": "2026-10-20",
                "extend_reason": "el cliente sigue esperando la aprobación del crédito"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cotización, with its new validity window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "Q-47",
                    "display_seq": 47,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "list_price_clp": 12500000,
                    "offered_price_clp": 11900000,
                    "tax_treatment": "afecto",
                    "discount_pct": 4.8,
                    "unit_make": "Toyota",
                    "unit_model": "Corolla",
                    "unit_version": "XEI 2.0 CVT",
                    "unit_year": 2023,
                    "unit_plate": "RJKL48",
                    "unit_vin": "MMSDA31S6M0123457",
                    "unit_odometer_value": 18500,
                    "unit_odometer_unit": "km",
                    "unit_color": "Gris plata",
                    "discount_hidden": false,
                    "issued_at": "2026-09-08T14:02:11.000Z",
                    "expires_on": "2026-10-20",
                    "state": "vigente",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "extended_at": "2026-09-20T09:11:44.000Z",
                    "extend_reason": "el cliente sigue esperando la aprobación del crédito",
                    "extended_by": "11111111-0000-4000-8000-000000000001",
                    "archived_at": null,
                    "archive_reason": null,
                    "archived_by": null,
                    "created_at": "2026-09-08T14:02:11.000Z",
                    "updated_at": "2026-09-08T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/quotes/{id}/archive": {
      "post": {
        "summary": "Archive a cotización",
        "description": "Files the document away from the working register, **recording who did it and why**.\n\n**This is NOT a delete and NOT a state.** The folio survives, `GET /quotes/{id}` still resolves the document, and its `state` stays `vigente` | `expirada` | `nula` — archive is invisible to that derivation, so the three buckets stay disjoint and exhaustive (BR-217/218). The only effect is that `GET /quotes` stops returning it unless you pass `include_archived=true`.\n\nA voided or expired quote CAN be archived — filing a dead document away is the ordinary case, and it is the difference from `/extend`, which a void forbids. The **first archive stands**: re-archiving is a 409 rather than an overwrite of the original actor and reason.\n\n`archive_reason` is mandatory and must contain VISIBLE text. The actor comes from the authenticated principal, and this body is STRICT — a body-supplied `archived_by` is a 400. Requires `quotes:write`: filing a document away withdraws nothing and is strictly weaker than a void.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Quotes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^Q-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "archive_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "archive_reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "archive_reason": "cotización antigua, ya no vigente en el seguimiento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The archived cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000003",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "Q-31",
                    "display_seq": 31,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "list_price_clp": 12500000,
                    "offered_price_clp": 11900000,
                    "tax_treatment": "afecto",
                    "discount_pct": 4.8,
                    "unit_make": "Toyota",
                    "unit_model": "Corolla",
                    "unit_version": "XEI 2.0 CVT",
                    "unit_year": 2023,
                    "unit_plate": "RJKL48",
                    "unit_vin": "MMSDA31S6M0123457",
                    "unit_odometer_value": 18500,
                    "unit_odometer_unit": "km",
                    "unit_color": "Gris plata",
                    "discount_hidden": false,
                    "issued_at": "2026-09-08T14:02:11.000Z",
                    "expires_on": "2026-08-01",
                    "state": "expirada",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "extended_at": null,
                    "extend_reason": null,
                    "extended_by": null,
                    "archived_at": "2026-09-10T08:00:00.000Z",
                    "archive_reason": "cotización antigua, ya no vigente en el seguimiento",
                    "archived_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-08T14:02:11.000Z",
                    "updated_at": "2026-09-08T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/iva": {
      "get": {
        "summary": "Read the IVA rate and the dealership default regime",
        "description": "Returns the **pinned platform rate** and the dealership’s default regime.\n\n`rate` is `{ bps: 1900, percent: 19, configurable: false, copy }`. The rate is **law, not a setting** — there is no endpoint that changes it, and `copy` is the string the UI must render beside it so an operator can see that it is fixed rather than a field somebody forgot to enable.\n\n`tenant_default.configured = false` means **nobody has chosen yet**, which is a different claim from having chosen `afecto`. A unit taken into stock while the default is unconfigured captures nothing, and resolves as `platform_fallback`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "IVA"
        ],
        "responses": {
          "200": {
            "description": "The pinned rate + the dealership default regime",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "rate": {
                      "bps": 1900,
                      "percent": 19,
                      "configurable": false,
                      "copy": "IVA legal de Chile (19%). No es configurable: aplica a todos los cálculos fiscales de la plataforma."
                    },
                    "tenant_default": {
                      "regime": "exento",
                      "configured": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/iva/tenant-default": {
      "put": {
        "summary": "Set the dealership default IVA regime",
        "description": "Sets the regime **new** units inherit when they enter stock (`afecto` | `exento`).\n\n**It is not retroactive.** A unit captures the default at intake and keeps it, so changing this moves the default for the next car and rewrites nothing that already resolved a regime — including closed months. Units that never captured one (imported before this existed) do follow it, and `GET /iva/vehicles/{id}` reports that as `source: tenant_default` rather than passing it off as a decision.\n\nThe body is `.strict()`: an unknown key is a **400**, because `regimen` is one character from `regime` and a silently discarded key is a caller who believes the dealership switched regimes when it did not. Clearing the default is deliberately NOT offered — that is a state to arrive at by never configuring, not one to navigate to.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "IVA"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "regime": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento"
                    ]
                  }
                },
                "required": [
                  "regime"
                ],
                "additionalProperties": false
              },
              "example": {
                "regime": "exento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored dealership default",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "regime": "exento",
                    "configured": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/iva/vehicles/{id}": {
      "get": {
        "summary": "Resolve the IVA regime for one unit",
        "description": "The unit’s financial-view payload: `stored_regime` (the raw column — `null` when this car never captured one), the resolved `regime`, and `source`, which says where the answer came from:\n\n* `vehicle` — captured on the unit. A change to the dealership default **cannot** move it.\n* `tenant_default` — inherited, so it **will** move if the default moves.\n* `platform_fallback` — nobody has decided at either grain; `determined` is `false`.\n\nAlso returns `debito` (`applies` + `rate_bps`) and `badge` — `label`, `qualifier` and the débito **consequence** spelled out in one sentence, which is the half worth showing: the regime only matters because of what it does to the margin.\n\nA vehicle in another workspace, soft-deleted, merged away, or nonexistent is a **404**, all four identically.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "IVA"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The resolved regime for the unit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "stored_regime": null,
                    "tenant_default": {
                      "regime": "exento",
                      "configured": true
                    },
                    "regime": "exento",
                    "source": "tenant_default",
                    "determined": true,
                    "captured": false,
                    "debito": {
                      "applies": false,
                      "rate_bps": 0
                    },
                    "rate": {
                      "bps": 1900,
                      "percent": 19,
                      "configurable": false,
                      "copy": "IVA legal de Chile (19%). No es configurable: aplica a todos los cálculos fiscales de la plataforma."
                    },
                    "badge": {
                      "label": "Exento",
                      "qualifier": "sin IVA",
                      "consequence": "La venta no genera débito fiscal de IVA (venta de autos usados, exenta en Chile): no se descuenta IVA del margen al calcular la comisión ni el neto del vendedor."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Override or clear one unit’s IVA regime",
        "description": "Sets this car’s **own** regime, overriding whatever it inherited — the ordinary case being one used car in an otherwise afecto lot, or the reverse.\n\n`regime: null` **clears** the override so the unit inherits the dealership default again. That is a real operation, not a no-op, and it is the only way back from an override set by mistake. Omitting the key entirely is a **400** — clearing an override and sending an empty body must not be the same request.\n\nRequires `dealership_economics:write`, **not** `marketplace:write`: a regime decides whether a stored amount is net or gross, so it is not editable by every principal who can edit stock.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "IVA"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "regime": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "afecto",
                      "exento"
                    ]
                  }
                },
                "required": [
                  "regime"
                ],
                "additionalProperties": false
              },
              "example": {
                "regime": "afecto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resolved regime after the write",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "stored_regime": "afecto",
                    "tenant_default": {
                      "regime": "exento",
                      "configured": true
                    },
                    "regime": "afecto",
                    "source": "vehicle",
                    "determined": true,
                    "captured": true,
                    "debito": {
                      "applies": true,
                      "rate_bps": 1900
                    },
                    "rate": {
                      "bps": 1900,
                      "percent": 19,
                      "configurable": false,
                      "copy": "IVA legal de Chile (19%). No es configurable: aplica a todos los cálculos fiscales de la plataforma."
                    },
                    "badge": {
                      "label": "Afecto",
                      "qualifier": "con IVA",
                      "consequence": "La venta genera débito fiscal de IVA (19%): se descuenta IVA del margen al calcular la comisión y el neto del vendedor."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/purchase-notes": {
      "get": {
        "summary": "List notas de compra",
        "description": "The dealership’s PURCHASE documents, newest first by `issued_at`. A nota de compra is the instrument that CREATES a cost — the unit’s cost basis comes from it, linked through `vehicle_acquisition.purchase_note_id`.\n\n**Voided documents are INCLUDED by default** — a voided nota de compra keeps its folio and stays listable so \"where is nota de compra #47?\" has an answer (ADR 0058 §10, BR-650–655). Pass `exclude_voided=true` to see only live ones; only the literal string `true` opts in, so `exclude_voided=false` does not hide them.\n\nLine items are NOT included here — a list of totals should not fan out into every document’s breakdown. Use `GET /purchase-notes/{id}` for that.\n\n**Each row carries the unit it bought**: `vehicle` is `{ id, make, model, year, registration_number }` or `null`, and `vehicle_count` says how many units the document acquired. One nota de compra may acquire SEVERAL units, so `vehicle` is the FIRST of them (deterministic: `acquired_on`, then `created_at`, then `id`) and `vehicle_count` is what keeps that from reading as \"this note bought one car\". `vehicle` is `null` exactly when `vehicle_count` is `0`, which is an ordinary state: the document is issued before anything is attached to it, and attaching is a separate act on `/vehicle-acquisitions`.\n\nUnknown query parameters are a **400**, not a silent drop: a filter the caller believes is being applied, returning a full unfiltered list with a 200, is a worse answer than a refusal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "exclude_voided",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "seller_contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Notas de compra",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d5d5d5d5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "P-9",
                      "display_seq": 9,
                      "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                      "net_clp": 6200000,
                      "tax_clp": null,
                      "tax_treatment": "no_gravado",
                      "status": "issued",
                      "issued_at": "2026-09-12T10:15:00.000Z",
                      "corrected_at": null,
                      "corrected_by": null,
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "created_at": "2026-09-12T10:15:00.000Z",
                      "updated_at": "2026-09-12T10:15:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Issue a nota de compra",
        "description": "Issues the document and allocates its folio (`P-1`, `P-2`, …) inside the inserting transaction, so a failed issue RELEASES the number rather than burning it out of a legally accountable series.\n\n**Money is whole CLP pesos** (`bigint`, never minor units, never decimals) and the triple is `net_clp` + `tax_clp` + `tax_treatment`, on the header AND again per line. Omit `tax_clp` to record \"not recorded\"; pass `0` to record \"no tax\" — ADR 0058 §2 makes those two different claims and nothing collapses them. `tax_treatment` is REQUIRED and has no default: the same peso falls inside or outside the IVA base depending on how the deal is structured.\n\n**`lines` are `document_line` rows**, written in the SAME transaction as the document, so a folio is never spent on a document whose breakdown never arrived. The array order IS the document order; positions are assigned by the database and cannot be supplied. Omit `lines` for a document with a header amount and no breakdown, which is complete and ordinary. Whether the lines add up to the header is deliberately NOT enforced.\n\n**This does NOT attach the document to a vehicle.** Recording that a unit arrived under this note is a separate act on `/vehicle-acquisitions`, because the ownership regime is imposed by the document and never chosen (BR-1446) — and one nota de compra may acquire several units.\n\n`seller_contact_id` must belong to the calling workspace; a foreign id and a nonexistent one answer identically (404), deliberately, because a difference between them is an existence oracle. Unknown body keys are a **400** — `tax` is one character from `tax_clp`, and silently discarding it would record \"no IVA was entered\" for a caller who entered it. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "seller_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "net_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado",
                      "pass_through"
                    ]
                  },
                  "issued_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Internal free-text notes. Never printed on the document. Blank clears them."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "description": {
                          "type": "string",
                          "maxLength": 500
                        },
                        "net_clp": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "tax_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "tax_treatment": {
                          "type": "string",
                          "enum": [
                            "afecto",
                            "exento",
                            "no_gravado",
                            "pass_through"
                          ]
                        }
                      },
                      "required": [
                        "description",
                        "net_clp",
                        "tax_treatment"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 200
                  },
                  "acquisition": {
                    "type": "object",
                    "properties": {
                      "vehicle_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "acquired_on": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "tax_clp": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 999999999999
                      },
                      "tax_treatment": {
                        "type": "string",
                        "enum": [
                          "afecto",
                          "exento",
                          "no_gravado",
                          "pass_through"
                        ]
                      },
                      "borne_by": {
                        "type": "string",
                        "enum": [
                          "automotora",
                          "cliente"
                        ]
                      },
                      "generates_fiscal_credit": {
                        "type": "boolean"
                      },
                      "retentions": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "category_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "net_clp": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 999999999999
                            },
                            "tax_clp": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 0,
                              "maximum": 999999999999
                            },
                            "tax_treatment": {
                              "type": "string",
                              "enum": [
                                "afecto",
                                "exento",
                                "no_gravado",
                                "pass_through"
                              ]
                            },
                            "incurred_on": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "supplier_contact_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "document_ref": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 200
                            }
                          },
                          "required": [
                            "category_id",
                            "net_clp",
                            "tax_treatment"
                          ],
                          "additionalProperties": false
                        },
                        "maxItems": 200
                      }
                    },
                    "required": [
                      "vehicle_id",
                      "acquired_on",
                      "tax_treatment",
                      "borne_by",
                      "generates_fiscal_credit"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "net_clp",
                  "tax_treatment"
                ],
                "additionalProperties": false
              },
              "example": {
                "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                "net_clp": 6200000,
                "tax_treatment": "no_gravado",
                "lines": [
                  {
                    "description": "Compra vehículo usado — Mazda CX-5 2020",
                    "amount_clp": 6200000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The issued nota de compra, with its lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "P-9",
                    "display_seq": 9,
                    "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                    "net_clp": 6200000,
                    "tax_clp": null,
                    "tax_treatment": "no_gravado",
                    "status": "issued",
                    "issued_at": "2026-09-12T10:15:00.000Z",
                    "corrected_at": null,
                    "corrected_by": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-12T10:15:00.000Z",
                    "updated_at": "2026-09-12T10:15:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/purchase-notes/{id}": {
      "get": {
        "summary": "Fetch one nota de compra",
        "description": "Accepts the UUID **or the folio** (`P-47`) — the folio is the form a dealer actually has in hand. Only the `P-` series resolves here; another entity’s display id (`V-47`, `T-1247`) is not a nota de compra.\n\nReturns the document WITH its line items in document order. `corrected_at` / `corrected_by` say whether it has been changed since it was issued, and by whom.\n\nIt also carries the unit it bought, the same as each row of the list: `vehicle` is `{ id, make, model, year, registration_number }` or `null`, and `vehicle_count` says how many units the document acquired (`vehicle` is the FIRST of them when there are several, and `null` exactly when the count is `0`).\n\nA document belonging to another workspace is a 404, indistinguishable from one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^P-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The nota de compra and its lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "P-9",
                    "display_seq": 9,
                    "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                    "net_clp": 6200000,
                    "tax_clp": null,
                    "tax_treatment": "no_gravado",
                    "status": "issued",
                    "issued_at": "2026-09-12T10:15:00.000Z",
                    "corrected_at": null,
                    "corrected_by": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-12T10:15:00.000Z",
                    "updated_at": "2026-09-12T10:15:00.000Z",
                    "vehicle": {
                      "id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "make": "Mazda",
                      "model": "CX-5",
                      "year": 2020,
                      "registration_number": "KXPT21"
                    },
                    "vehicle_count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Correct a nota de compra",
        "description": "Corrects a LIVE document and **stamps it**: `corrected_at` and the acting principal are written onto the row, so a reader of the document can tell it has been changed since it was issued without joining an audit table. The folio and the status are never touched.\n\n**An ABSENT field is left alone; an explicit `null` CLEARS it.** On `tax_clp` that is the difference between \"not recorded\" and \"no tax\", which ADR 0058 §2 keeps apart — so correcting away an amount entered by mistake is expressible without being confused with zero.\n\n`lines` REPLACES the whole breakdown, in order: pass the complete list, or omit the field to leave the existing lines untouched. `[]` clears them, which is a legitimate correction of a breakdown entered against the wrong note. The replacement is delete-all-then-reinsert inside one transaction, because the per-document ordering key is not deferrable.\n\nA **voided** document cannot be corrected — that is a **409**, because voiding withdraws the instrument and correcting a withdrawn accountable record would rewrite something a third party may already have been shown. Issue a replacement instead; the voided one keeps its folio and stays listable.\n\nA correction must state at least one field — an empty body is a **400**, not a no-op stamp. `PATCH` and not `PUT`: the body is a partial and `absent` vs `null` are different instructions, which replace-the-representation semantics cannot express. Requires `purchase_notes:write`, the same authority as issuing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^P-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "seller_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "net_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado",
                      "pass_through"
                    ]
                  },
                  "issued_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Internal free-text notes. Never printed on the document. Blank clears them."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "description": {
                          "type": "string",
                          "maxLength": 500
                        },
                        "net_clp": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "tax_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "tax_treatment": {
                          "type": "string",
                          "enum": [
                            "afecto",
                            "exento",
                            "no_gravado",
                            "pass_through"
                          ]
                        }
                      },
                      "required": [
                        "description",
                        "net_clp",
                        "tax_treatment"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 200
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "net_clp": 6150000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The corrected nota de compra, with its lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "P-9",
                    "display_seq": 9,
                    "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                    "net_clp": 6150000,
                    "tax_clp": null,
                    "tax_treatment": "no_gravado",
                    "status": "issued",
                    "issued_at": "2026-09-12T10:15:00.000Z",
                    "corrected_at": "2026-09-13T09:00:00.000Z",
                    "corrected_by": "11111111-0000-4000-8000-000000000001",
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-12T10:15:00.000Z",
                    "updated_at": "2026-09-12T10:15:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/purchase-notes/{id}/pdf": {
      "get": {
        "summary": "Print the nota de compra",
        "description": "The nota de compra as a printable PDF (`application/pdf`, served `inline`) on the dealership’s paper, drawn from the workspace’s default nota de compra template (the standard text when there is none): folio, the seller and the dealership with RUT and domicilio, the vehicle, the price with its retentions and the amount paid to the seller, detail lines and signatures. A fact the seller’s contact lacks prints «【PENDIENTE: …】» rather than refusing the print. Rendered on demand and not stored. Any status prints; a voided note says «Anulada». Accepts the UUID or the folio (`P-47`). Requires `purchase_notes:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^P-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The printable nota de compra",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/purchase-notes/{id}/void": {
      "post": {
        "summary": "Void a nota de compra",
        "description": "Voids the document. It is **never deleted and it KEEPS ITS FOLIO** (ADR 0058 §10, BR-650–655): the row records the time, the reason and the acting principal, keeps its line items and its correction mark, and remains listable. There is deliberately no `DELETE` verb on this resource.\n\n`void_reason` is mandatory and must contain at least one **letter or digit** — a reason made only of whitespace, punctuation, symbols or invisible characters states nothing and is refused, at the edge and by a table CHECK.\n\nThe actor comes from the authenticated principal; a `voided_by` in the body is IGNORED rather than rejected — a self-declared actor is not an attestation. Voiding twice is a **409**: the first actor and reason stand.\n\nRequires `purchase_notes:void`, which is a DIFFERENT authority from `purchase_notes:write` — holding the write scope does not permit a void.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Purchase Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^P-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "void_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "void_reason"
                ]
              },
              "example": {
                "void_reason": "se anuló la compra: el vendedor desistió antes de firmar"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The voided nota de compra",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "P-9",
                    "display_seq": 9,
                    "seller_contact_id": "22222222-0000-4000-8000-000000000003",
                    "net_clp": 6200000,
                    "tax_clp": null,
                    "tax_treatment": "no_gravado",
                    "status": "voided",
                    "issued_at": "2026-09-12T10:15:00.000Z",
                    "corrected_at": null,
                    "corrected_by": null,
                    "voided_at": "2026-09-14T11:30:00.000Z",
                    "void_reason": "se anuló la compra: el vendedor desistió antes de firmar",
                    "voided_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-12T10:15:00.000Z",
                    "updated_at": "2026-09-12T10:15:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/document-templates/starters": {
      "get": {
        "summary": "The standard text of each document family",
        "description": "The starter Spanish body of every document family (nota de venta, comprobante de reserva, cotización, mandato de consignación, nota de compra, ficha técnica) — the text an authoring screen opens with when the dealer starts a template, and what «volver al texto estándar» appends as a new version (`PUT /document-templates/{id}`). Every body uses only variables the family’s vocabulary registers, so it saves as-is. Every automotive workspace is seeded with these as its default templates (one per family it had none for). `kind` narrows to one family; `placeholders` lists the `{{NAMES}}` the body uses. Requires `document_templates:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Document Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 60,
              "pattern": "^[a-z][a-z0-9_]*$"
            },
            "required": false,
            "name": "kind",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Starter templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "document_kind": "quote",
                      "label": "Cotización",
                      "key": "cotizacion",
                      "name": "Cotización",
                      "body": "COTIZACIÓN N° {{FOLIO}}\n\n{{MANDATARIA_RAZON_SOCIAL}}\nRUT {{MANDATARIA_RUT}}\n\nCLIENTE\n{{CLIENTE_NOMBRE}}, RUT {{CLIENTE_RUT}}.\n\nPRECIO\nPrecio ofrecido: {{PRECIO_OFERTADO}}",
                      "placeholders": [
                        "FOLIO",
                        "MANDATARIA_RAZON_SOCIAL",
                        "MANDATARIA_RUT",
                        "CLIENTE_NOMBRE",
                        "CLIENTE_RUT",
                        "PRECIO_OFERTADO"
                      ]
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/document-templates/clauses/check": {
      "post": {
        "summary": "Check a body against its cláusulas esenciales and recomendadas",
        "description": "The editor's checklist («Esenciales 5/5 · Recomendadas 5/6», ADR 0114 §6.1), computed by the same function the publish gate uses. Send the body being written plus `template_id` (it is that template's next version: its current version is the baseline for \"removed\") or `document_kind` (a new template: the standard text is the baseline). Returns each clause with `present`, a `summary` of counts, `missing_esenciales` (publishing is refused while non-empty) and `removed_recomendadas` (each needs a reason in `recommended_clause_removals` when publishing). A clause is detected by structural markers — the `{{VARIABLES}}` it needs (designed blocks count, through their text twins), a section heading, or a signature block — never by free-text search. Writes nothing. Requires `document_templates:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Document Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "maxLength": 100000
                  },
                  "template_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "document_kind": {
                    "type": "string",
                    "maxLength": 60,
                    "pattern": "^[a-z][a-z0-9_]*$"
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": false
              },
              "example": {
                "template_id": "3b1f8a52-9d34-4a7e-8f2c-1a2b3c4d5e6f",
                "body": "# Nota de venta N° {{FOLIO}}\n\n{{BLOQUE_PARTES}}\n\n{{BLOQUE_VEHICULO}}\n\n{{BLOQUE_PRECIO}}\n\n{{BLOQUE_FIRMAS}}"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The clause checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "document_kind": "sale_note",
                    "esenciales": [
                      {
                        "key": "partes",
                        "label": "Partes con RUT y domicilio",
                        "tier": "esencial",
                        "present": true,
                        "description": "Partes con RUT y domicilio."
                      },
                      {
                        "key": "vehiculo",
                        "label": "Identidad del vehículo",
                        "tier": "esencial",
                        "present": true,
                        "description": "Identidad del vehículo."
                      },
                      {
                        "key": "precio",
                        "label": "Precio en cifras y palabras",
                        "tier": "esencial",
                        "present": true,
                        "description": "Precio en cifras y palabras."
                      },
                      {
                        "key": "pago",
                        "label": "Forma de pago",
                        "tier": "esencial",
                        "present": true,
                        "description": "Forma de pago."
                      },
                      {
                        "key": "firmas",
                        "label": "Firmas",
                        "tier": "esencial",
                        "present": true,
                        "description": "Firmas."
                      }
                    ],
                    "recomendadas": [
                      {
                        "key": "folio",
                        "label": "Número de folio",
                        "tier": "recomendada",
                        "present": true,
                        "description": "Número de folio."
                      },
                      {
                        "key": "vendedor",
                        "label": "Vendedor de la automotora",
                        "tier": "recomendada",
                        "present": true,
                        "description": "Vendedor de la automotora."
                      },
                      {
                        "key": "chasis",
                        "label": "N° de chasis (VIN)",
                        "tier": "recomendada",
                        "present": true,
                        "description": "N° de chasis (VIN)."
                      },
                      {
                        "key": "kilometraje",
                        "label": "Kilometraje",
                        "tier": "recomendada",
                        "present": true,
                        "description": "Kilometraje."
                      },
                      {
                        "key": "impuesto_transferencia",
                        "label": "Impuesto a la transferencia",
                        "tier": "recomendada",
                        "present": true,
                        "description": "Impuesto a la transferencia."
                      },
                      {
                        "key": "condiciones",
                        "label": "Condiciones",
                        "tier": "recomendada",
                        "present": false,
                        "description": "Condiciones."
                      }
                    ],
                    "summary": {
                      "esenciales": {
                        "present": 5,
                        "total": 5
                      },
                      "recomendadas": {
                        "present": 5,
                        "total": 6
                      }
                    },
                    "missing_esenciales": [],
                    "removed_recomendadas": [
                      {
                        "key": "condiciones",
                        "label": "Condiciones",
                        "description": "La sección «Condiciones» con los términos del documento.",
                        "tier": "recomendada",
                        "present": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/document-templates/{id}/preview-values": {
      "get": {
        "summary": "Placeholder values of the latest issued document of a template's type",
        "description": "What the template preview renders against: the `{{NAME}}` values of the most recent documento emitido of the template's document type, built by the SAME code that prints that document, so the preview cannot drift from the print. Covers `sale_note`, `compraventa`, `deal_settlement` (Resumen del negocio), `consignment` (mandato), `consignment_settlement` (liquidación) and `purchase_note` (nota de compra); reserva, cotización and ficha técnica print fixed layouts and have no values source. The designed blocks (`BLOQUE_PARTES`, `BLOQUE_VEHICULO`, `BLOQUE_PRECIO`, `BLOQUE_FIRMAS`) are values too: each is the block's text form filled with the same document's values (a fact the document lacks reads «【PENDIENTE: …】»). `source` names the document (`display_id`: the folio such as `V-12`, or the patente for a consignment, and when it was issued); it is `null` — with an empty `values` — when none exists or the type has no values source; the caller then falls back to the vocabulary examples. A fact the document lacks is absent from `values`, never blank. The values carry buyer/owner identity and money, so this requires `document_templates:read` AND the read scope of that document (`sale_notes:read` for nota de venta and compraventa, `consignments:read` for the two consignment families, `dealership_economics:read` for the Resumen del negocio, `purchase_notes:read` for the nota de compra). Writes and issues nothing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Document Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The source document and its placeholder values",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "document_kind": "sale_note",
                    "source": {
                      "document_kind": "sale_note",
                      "id": "5c1a3f0e-8b7d-4e2a-9c61-2d4f6a8b0c13",
                      "display_id": "V-12",
                      "issued_at": "2026-09-29T15:20:00.000Z"
                    },
                    "values": {
                      "FOLIO": "V-12",
                      "FECHA": "29 de septiembre de 2026",
                      "COMPRADOR_NOMBRE": "Persona Ejemplo",
                      "VEHICULO_PATENTE": "AB-CD-12"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/credit-applications": {
      "get": {
        "summary": "The chase board",
        "description": "Filterable by the DERIVED `state` (never a `submitted=true`-style flag over the raw facts), `vehicle_id`, `buyer_contact_id`, `institution_id` and the recording PERIOD (`created_from` / `created_to`, independent and INCLUSIVE DEALERSHIP calendar days — America/Santiago, not UTC; a `created_from` after `created_to` is a 400, not an empty page). There is deliberately no free-text search and no sort parameter. Each row carries the lender’s name, the car’s label and patente, the borrower’s name and RUT, and — when the file is taken — the live credit’s state and disbursement instant. ⚠ TWO FIELD GATES, both ABSENT-never-null: `fee_clp` / `financed_total_clp` need `dealership_economics:read` (the house’s commission), and `buyer_rut` needs `contacts:read` (it IS `contact.tax_id`, gated exactly as the contacts family gates it). An absent key is a statement about the READER, never about the row.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "recorded",
                "submitted",
                "approved",
                "rejected",
                "withdrawn"
              ]
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "buyer_contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "institution_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "created_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "created_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Credit applications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d9d9d9d9-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "F-12",
                      "display_seq": 12,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                      "institution_id": "dadadada-0000-4000-8000-000000000001",
                      "requested_amount_clp": 9500000,
                      "vehicle_price_clp": 11900000,
                      "declared_income_clp": 1200000,
                      "fee_clp": null,
                      "term_months": 48,
                      "financed_total_clp": null,
                      "submitted_at": "2026-09-10T14:00:00.000Z",
                      "submitted_by": "11111111-0000-4000-8000-000000000001",
                      "decided_at": null,
                      "decided_by": null,
                      "outcome": null,
                      "decision_reason": null,
                      "approved_amount_clp": null,
                      "withdrawn_at": null,
                      "withdrawn_by": null,
                      "withdrawn_reason": null,
                      "last_contact_at": null,
                      "last_contact_by": null,
                      "chased_at": null,
                      "state": "submitted",
                      "created_at": "2026-09-08T11:00:00.000Z",
                      "updated_at": "2026-09-10T14:00:00.000Z",
                      "taken": false,
                      "taken_sale_note_id": null,
                      "taken_sale_note_folio": null,
                      "institution_name": "Banco Estado",
                      "taken_credit_state": null,
                      "taken_disbursed_at": null,
                      "vehicle_label": "Toyota Corolla 2023",
                      "vehicle_plate": "RJKL48",
                      "buyer_name": "María José Soto",
                      "buyer_rut": "12.345.678-9"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record a dossier",
        "description": "Allocates the folio `F-n`. No fact beyond the three links and the requested terms is settable at creation — no `submitted_at`, no `outcome`, no `state`. Recording that the carpeta went out is its own act.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "buyer_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "institution_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "requested_amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "vehicle_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "declared_income_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "fee_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "term_months": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 600
                  }
                },
                "required": [
                  "vehicle_id",
                  "buyer_contact_id",
                  "institution_id",
                  "requested_amount_clp"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                "institution_id": "dadadada-0000-4000-8000-000000000001",
                "requested_amount_clp": 9500000,
                "vehicle_price_clp": 11900000,
                "declared_income_clp": 1200000,
                "term_months": 48
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded dossier",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": null,
                    "submitted_by": null,
                    "decided_at": null,
                    "decided_by": null,
                    "outcome": null,
                    "decision_reason": null,
                    "approved_amount_clp": null,
                    "withdrawn_at": null,
                    "withdrawn_by": null,
                    "withdrawn_reason": null,
                    "last_contact_at": null,
                    "last_contact_by": null,
                    "chased_at": null,
                    "state": "recorded",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/credit-applications/ageing": {
      "get": {
        "summary": "The chase board with its clock resolved (ERP S11-4)",
        "description": "Mounted before `/:id` — declaration order matters, or this literal segment would be swallowed by the id route. Read-scoped: nothing here changes a row. There is no `threshold_days` parameter — the SLA window lives on the financial institution, per lender, not as a caller override.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "reciente",
                "atrasado",
                "vencido"
              ]
            },
            "required": false,
            "name": "bucket",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "dealership",
                "institution",
                "customer"
              ]
            },
            "required": false,
            "name": "waiting_on",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Ageing board",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d9d9d9d9-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "F-12",
                      "display_seq": 12,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                      "institution_id": "dadadada-0000-4000-8000-000000000001",
                      "requested_amount_clp": 9500000,
                      "vehicle_price_clp": 11900000,
                      "declared_income_clp": 1200000,
                      "fee_clp": null,
                      "term_months": 48,
                      "financed_total_clp": null,
                      "submitted_at": "2026-09-10T14:00:00.000Z",
                      "submitted_by": "11111111-0000-4000-8000-000000000001",
                      "decided_at": null,
                      "decided_by": null,
                      "outcome": null,
                      "decision_reason": null,
                      "approved_amount_clp": null,
                      "withdrawn_at": null,
                      "withdrawn_by": null,
                      "withdrawn_reason": null,
                      "last_contact_at": null,
                      "last_contact_by": null,
                      "chased_at": null,
                      "state": "submitted",
                      "created_at": "2026-09-08T11:00:00.000Z",
                      "updated_at": "2026-09-10T14:00:00.000Z",
                      "taken": false,
                      "taken_sale_note_id": null,
                      "taken_sale_note_folio": null,
                      "institution_name": "Banco Estado",
                      "taken_credit_state": null,
                      "taken_disbursed_at": null,
                      "vehicle_label": "Toyota Corolla 2023",
                      "vehicle_plate": "RJKL48",
                      "buyer_name": "María José Soto",
                      "buyer_rut": "12.345.678-9",
                      "response_sla_days": 5,
                      "days_since_last_contact": 9,
                      "overdue": true
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/credit-applications/{id}": {
      "get": {
        "summary": "Fetch one file",
        "description": "Accepts the UUID or the folio (`F-12`). Same enriched shape as the list, including the same two field gates: `fee_clp` / `financed_total_clp` absent without `dealership_economics:read`, `buyer_rut` absent without `contacts:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^F-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The credit application",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": "2026-09-10T14:00:00.000Z",
                    "submitted_by": "11111111-0000-4000-8000-000000000001",
                    "decided_at": null,
                    "decided_by": null,
                    "outcome": null,
                    "decision_reason": null,
                    "approved_amount_clp": null,
                    "withdrawn_at": null,
                    "withdrawn_by": null,
                    "withdrawn_reason": null,
                    "last_contact_at": null,
                    "last_contact_by": null,
                    "chased_at": null,
                    "state": "submitted",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/credit-applications/{id}/submission": {
      "post": {
        "summary": "Record that the carpeta went out",
        "description": "Records BR-1024’s SENT as a fact and moves the file from `recorded` to `submitted`. `submitted_at` is the CALLER’S and OPTIONAL — the ordinary case is recording a carpeta that already left, on a day the ejecutivo knows, not the instant this endpoint is called; omit it and the server stamps `now()`. The actor is the authenticated principal, never a body field. A file that is not `recorded` refuses with a **409** — this is a compare-and-swap on `submitted_at is null`, not a read-then-write, so two concurrent submissions resolve to one 200 and one 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^F-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "submitted_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "submitted_at": "2026-09-10T14:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated application",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": "2026-09-10T14:00:00.000Z",
                    "submitted_by": "11111111-0000-4000-8000-000000000001",
                    "decided_at": null,
                    "decided_by": null,
                    "outcome": null,
                    "decision_reason": null,
                    "approved_amount_clp": null,
                    "withdrawn_at": null,
                    "withdrawn_by": null,
                    "withdrawn_reason": null,
                    "last_contact_at": null,
                    "last_contact_by": null,
                    "chased_at": null,
                    "state": "submitted",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/credit-applications/{id}/decision": {
      "post": {
        "summary": "Record the financiera’s answer",
        "description": "Records the lender’s verdict — `outcome` is the only required field, since BR-1021’s other states are not verdicts (`recorded` and `submitted` are the absence of one, and `taken` is derived from the sale note rather than decided here). `decision_reason` is OPTIONAL even on a rejection, deliberately: a bank that gave no reason is a real case, and forcing the field would fabricate a \"sin motivo\" signal rather than capture a real one. `approved_amount_clp` only makes sense with `outcome: \"approved\"`.\n\nOnly legal on a `submitted` file — a **409** otherwise, since a decision needs a carpeta the lender has actually seen. There is no second decision: the outcome, once recorded, is terminal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^F-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "decided_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "decision_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "approved_amount_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999999999999
                  }
                },
                "required": [
                  "outcome"
                ],
                "additionalProperties": false
              },
              "example": {
                "outcome": "approved",
                "decision_reason": "ingresos y antigüedad laboral cumplen la política",
                "approved_amount_clp": 9200000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated application",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": "2026-09-10T14:00:00.000Z",
                    "submitted_by": "11111111-0000-4000-8000-000000000001",
                    "decided_at": "2026-09-16T10:00:00.000Z",
                    "decided_by": "11111111-0000-4000-8000-000000000001",
                    "outcome": "approved",
                    "decision_reason": "ingresos y antigüedad laboral cumplen la política",
                    "approved_amount_clp": 9200000,
                    "withdrawn_at": null,
                    "withdrawn_by": null,
                    "withdrawn_reason": null,
                    "last_contact_at": null,
                    "last_contact_by": null,
                    "chased_at": null,
                    "state": "approved",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/credit-applications/{id}/withdrawal": {
      "post": {
        "summary": "Pull the file (BR-1040’s NOT_TAKEN)",
        "description": "The HOUSE’S OWN act on its OWN record — the dealership stops chasing the file, whatever the lender said or did not say. There is deliberately no `withdrawn_at` in the body: the instant is server-stamped `now()`, the same as the decision body’s actor rule, because this is not a fact reported from somebody else’s clock. `withdrawn_reason` is REQUIRED and must contain visible text.\n\n**The withdrawal is TERMINAL** — no further submission, decision or contact note may be recorded against it afterwards, so a second call is a **409** and the first actor and reason stand.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^F-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "withdrawn_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "withdrawn_reason": "el cliente obtuvo mejores condiciones en otra financiera"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The withdrawn application",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": "2026-09-10T14:00:00.000Z",
                    "submitted_by": "11111111-0000-4000-8000-000000000001",
                    "decided_at": null,
                    "decided_by": null,
                    "outcome": null,
                    "decision_reason": null,
                    "approved_amount_clp": null,
                    "withdrawn_at": "2026-09-18T09:00:00.000Z",
                    "withdrawn_by": "11111111-0000-4000-8000-000000000001",
                    "withdrawn_reason": "el cliente obtuvo mejores condiciones en otra financiera",
                    "last_contact_at": null,
                    "last_contact_by": null,
                    "chased_at": null,
                    "state": "withdrawn",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/credit-applications/{id}/contact": {
      "post": {
        "summary": "\"We called the bank about this file\" (ERP S11-4, BR-1053)",
        "description": "The ONE act that resets the ageing clock. Repeatable, unlike submission, decision and withdrawal, which each happen once. Empty body.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Credit Applications"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^F-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated application",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "F-12",
                    "display_seq": 12,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                    "institution_id": "dadadada-0000-4000-8000-000000000001",
                    "requested_amount_clp": 9500000,
                    "vehicle_price_clp": 11900000,
                    "declared_income_clp": 1200000,
                    "fee_clp": null,
                    "term_months": 48,
                    "financed_total_clp": null,
                    "submitted_at": "2026-09-10T14:00:00.000Z",
                    "submitted_by": "11111111-0000-4000-8000-000000000001",
                    "decided_at": null,
                    "decided_by": null,
                    "outcome": null,
                    "decision_reason": null,
                    "approved_amount_clp": null,
                    "withdrawn_at": null,
                    "withdrawn_by": null,
                    "withdrawn_reason": null,
                    "last_contact_at": "2026-09-19T15:30:00.000Z",
                    "last_contact_by": "11111111-0000-4000-8000-000000000001",
                    "chased_at": null,
                    "state": "submitted",
                    "created_at": "2026-09-08T11:00:00.000Z",
                    "updated_at": "2026-09-10T14:00:00.000Z",
                    "taken": false,
                    "taken_sale_note_id": null,
                    "taken_sale_note_folio": null,
                    "institution_name": "Banco Estado",
                    "taken_credit_state": null,
                    "taken_disbursed_at": null,
                    "vehicle_label": "Toyota Corolla 2023",
                    "vehicle_plate": "RJKL48",
                    "buyer_name": "María José Soto",
                    "buyer_rut": "12.345.678-9"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/organization-legal": {
      "get": {
        "summary": "This workspace’s legal entities",
        "description": "The dealership’s legal identity — razón social, RUT, giro, domicilio, representante legal and (optionally) the escritura de personería — the facts every printed document states about the house (nota de venta, compraventa, mandato de consignación). A LIST, 0 or 1 entries in practice: an empty list is what makes printing answer 409 `automotora_identity_missing` / «Falta la identidad legal de la automotora». Requires `organization_legal:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "responses": {
          "200": {
            "description": "Legal entities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "4e2a9c71-8b3d-4f5e-a1c6-2d7b9e0f3a58",
                      "tenant_id": "6c3f1a2b-9d8e-4b7a-8c5d-1e2f3a4b5c6d",
                      "legal_name": "Comercial Autos del Sur SpA",
                      "tax_id": "76543210-3",
                      "tax_id_kind": "rut",
                      "giro": "Compraventa de vehículos motorizados",
                      "address_street": "Av. Apoquindo",
                      "address_number": "5400",
                      "address_unit": null,
                      "comuna_code": "13114",
                      "region_code": "13",
                      "address_source": "manual",
                      "representative_name": "María Fernanda Rojas",
                      "representative_tax_id": "13456789-0",
                      "representative_tax_id_kind": "rut",
                      "escritura_date": "2019-03-12",
                      "escritura_notary": "Notaría de Santiago de don Pedro Ejemplo",
                      "escritura_comuna_code": "13101",
                      "created_at": "2026-09-28T13:00:00.000Z",
                      "updated_at": "2026-09-28T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record the house’s legal identity",
        "description": "Records the dealership’s legal identity. RUTs are accepted formatted or bare and stored normalised; the region is derived from the comuna. The escritura trio (`escritura_date`, `escritura_notary`, `escritura_comuna_code`) is all-or-nothing, and the representative’s RUT travels with its kind. With this row in place the nota de venta prints; the compraventa additionally needs the representative’s RUT and the escritura (or a poder). Requires `organization_legal:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "legal_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "tax_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "tax_id_kind": {
                    "type": "string",
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "giro": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "address_street": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": "string",
                    "pattern": "^[0-9]{5}$"
                  },
                  "representative_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "representative_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "representative_tax_id_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "escritura_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "escritura_notary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "escritura_comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  }
                },
                "required": [
                  "legal_name",
                  "tax_id",
                  "giro",
                  "address_street",
                  "comuna_code",
                  "representative_name"
                ]
              },
              "example": {
                "legal_name": "Comercial Autos del Sur SpA",
                "tax_id": "76.543.210-3",
                "giro": "Compraventa de vehículos motorizados",
                "address_street": "Av. Apoquindo",
                "address_number": "5400",
                "comuna_code": "13114",
                "representative_name": "María Fernanda Rojas",
                "representative_tax_id": "13.456.789-0",
                "representative_tax_id_kind": "rut",
                "escritura_date": "2019-03-12",
                "escritura_notary": "Notaría de Santiago de don Pedro Ejemplo",
                "escritura_comuna_code": "13101"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded entity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "4e2a9c71-8b3d-4f5e-a1c6-2d7b9e0f3a58",
                    "tenant_id": "6c3f1a2b-9d8e-4b7a-8c5d-1e2f3a4b5c6d",
                    "legal_name": "Comercial Autos del Sur SpA",
                    "tax_id": "76543210-3",
                    "tax_id_kind": "rut",
                    "giro": "Compraventa de vehículos motorizados",
                    "address_street": "Av. Apoquindo",
                    "address_number": "5400",
                    "address_unit": null,
                    "comuna_code": "13114",
                    "region_code": "13",
                    "address_source": "manual",
                    "representative_name": "María Fernanda Rojas",
                    "representative_tax_id": "13456789-0",
                    "representative_tax_id_kind": "rut",
                    "escritura_date": "2019-03-12",
                    "escritura_notary": "Notaría de Santiago de don Pedro Ejemplo",
                    "escritura_comuna_code": "13101",
                    "created_at": "2026-09-28T13:00:00.000Z",
                    "updated_at": "2026-09-28T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/organization-legal/{id}": {
      "get": {
        "summary": "Fetch one legal entity",
        "description": "One of this workspace’s legal entities by id. Another workspace’s is a 404. Requires `organization_legal:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The legal entity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "4e2a9c71-8b3d-4f5e-a1c6-2d7b9e0f3a58",
                    "tenant_id": "6c3f1a2b-9d8e-4b7a-8c5d-1e2f3a4b5c6d",
                    "legal_name": "Comercial Autos del Sur SpA",
                    "tax_id": "76543210-3",
                    "tax_id_kind": "rut",
                    "giro": "Compraventa de vehículos motorizados",
                    "address_street": "Av. Apoquindo",
                    "address_number": "5400",
                    "address_unit": null,
                    "comuna_code": "13114",
                    "region_code": "13",
                    "address_source": "manual",
                    "representative_name": "María Fernanda Rojas",
                    "representative_tax_id": "13456789-0",
                    "representative_tax_id_kind": "rut",
                    "escritura_date": "2019-03-12",
                    "escritura_notary": "Notaría de Santiago de don Pedro Ejemplo",
                    "escritura_comuna_code": "13101",
                    "created_at": "2026-09-28T13:00:00.000Z",
                    "updated_at": "2026-09-28T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a legal entity",
        "description": "Costs nothing today (nothing references this table by id yet), but is on a clock: the day a document records which entity it named, this starts refusing for a named entity. Cascades to its powers of attorney.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a legal entity",
        "description": "Sparse. The NOT NULL columns are optional but not nullable — sending `null` for one is a 400, not a clear.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "legal_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "tax_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "tax_id_kind": {
                    "type": "string",
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "giro": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "address_street": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "address_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "address_unit": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "comuna_code": {
                    "type": "string",
                    "pattern": "^[0-9]{5}$"
                  },
                  "representative_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "representative_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "representative_tax_id_kind": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "rut",
                      "passport",
                      "foreign_tax_id"
                    ]
                  },
                  "escritura_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "escritura_notary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "escritura_comuna_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{5}$"
                  }
                }
              },
              "example": {
                "giro": "Compraventa y consignación de vehículos motorizados"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated entity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "4e2a9c71-8b3d-4f5e-a1c6-2d7b9e0f3a58",
                    "tenant_id": "6c3f1a2b-9d8e-4b7a-8c5d-1e2f3a4b5c6d",
                    "legal_name": "Comercial Autos del Sur SpA",
                    "tax_id": "76543210-3",
                    "tax_id_kind": "rut",
                    "giro": "Compraventa y consignación de vehículos motorizados",
                    "address_street": "Av. Apoquindo",
                    "address_number": "5400",
                    "address_unit": null,
                    "comuna_code": "13114",
                    "region_code": "13",
                    "address_source": "manual",
                    "representative_name": "María Fernanda Rojas",
                    "representative_tax_id": "13456789-0",
                    "representative_tax_id_kind": "rut",
                    "escritura_date": "2019-03-12",
                    "escritura_notary": "Notaría de Santiago de don Pedro Ejemplo",
                    "escritura_comuna_code": "13101",
                    "created_at": "2026-09-28T13:00:00.000Z",
                    "updated_at": "2026-09-28T14:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/organization-legal/{organizationLegalId}/powers-of-attorney": {
      "get": {
        "summary": "List the poderes granted for this legal entity",
        "description": "The powers of attorney (poderes) on file for one of this workspace’s legal entities, revoked and expired ones included. When a poder is in force on the signing date, printing names its delegate as the representative, so the entity’s own representante RUT and escritura are no longer required. Requires `organization_legal:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Organization Legal"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "organizationLegalId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Powers of attorney",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "9b1d4c7e-2f3a-4e5b-8c6d-7a8b9c0d1e2f",
                      "tenant_id": "6c3f1a2b-9d8e-4b7a-8c5d-1e2f3a4b5c6d",
                      "organization_legal_id": "4e2a9c71-8b3d-4f5e-a1c6-2d7b9e0f3a58",
                      "delegate_name": "Andrea Soto Reyes",
                      "delegate_tax_id": "12345678-5",
                      "delegate_tax_id_kind": "rut",
                      "escritura_date": "2024-03-12",
                      "escritura_notary": "Notaría de ejemplo",
                      "escritura_comuna_code": "13132",
                      "escritura_comuna_name": "Vitacura",
                      "valid_from": "2024-03-12",
                      "valid_until": null,
                      "revoked_at": null,
                      "revoked_by": null,
                      "revoked_reason": null,
                      "created_at": "2024-03-14T15:02:11.000Z",
                      "updated_at": "2024-03-14T15:02:11.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/participations": {
      "get": {
        "summary": "The credited participations on a sale, with totals",
        "description": "Who is credited on a nota de venta (by UUID or folio `V-47`) and what each earns: its **Vendedor** (`role: closer`, exactly one) and any **Colaborador** (`role: collaborator`). Each participation carries its `id` — the `participationId` that `PATCH /sale-notes/{id}/participations/{participationId}` takes — and its frozen commission (`commission_rate_bp` × `commission_basis_clp` = `commission_clp`). A participation whose three commission fields are `null` makes the sale «Sin comisión»: `totals.complete` is false, `totals.gaps` names who, and the sale’s Utilidad del negocio is not computable until it is recorded. Needs `seller_compensation:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Participations and totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "display_id": "V-23",
                    "participations": [
                      {
                        "id": "5d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "user_id": "11111111-0000-4000-8000-000000000001",
                        "role": "closer",
                        "commission_rate_bp": 250,
                        "commission_basis_clp": 1800000,
                        "commission_clp": 45000,
                        "created_at": "2026-09-14T11:30:00.000Z",
                        "updated_at": "2026-09-14T11:30:00.000Z"
                      },
                      {
                        "id": "6e2d3c4b-5f60-4b7c-9d8e-0f1a2b3c4d5e",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "user_id": "11111111-0000-4000-8000-000000000002",
                        "role": "collaborator",
                        "commission_rate_bp": null,
                        "commission_basis_clp": null,
                        "commission_clp": null,
                        "created_at": "2026-09-14T11:30:00.000Z",
                        "updated_at": "2026-09-14T11:30:00.000Z"
                      }
                    ],
                    "totals": {
                      "closer_commission_clp": 45000,
                      "collaborator_commission_clp": 0,
                      "total_commission_clp": 45000,
                      "complete": false,
                      "gaps": [
                        "11111111-0000-4000-8000-000000000002"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/{id}/participations/{participationId}": {
      "patch": {
        "summary": "Record the missing commission on a participation",
        "description": "Fixes a «Sin comisión» sale: a participation written with no commission — its Vendedor had no compensation scheme when the nota de venta was emitted, or the car had no recorded cost (no computable Margen bruto to apply a rate to) — gets one. `commission_rate_bp` defaults to the member’s scheme as of the SALE’s day; `commission_basis_clp` defaults to the sale’s Margen bruto as it stands now (so: record the cost, then the commission). **409** when the participation already has a commission — a recorded commission is frozen and never restated — when the Margen bruto is still not computable and no basis is stated, or on a voided nota de venta. **400** when no rate is stated and the member had no scheme on that day. **A fixed amount** agreed for this sale is `commission_clp` instead: it is recorded as 100 % of that amount (`commission_rate_bp` 10000, `commission_basis_clp` = the amount) and cannot be combined with the other two (**400**). Once every participation has its commission, the sale’s Utilidad del negocio is computable and it enters Margen y rentabilidad’s totals.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "participationId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commission_rate_bp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "commission_basis_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "commission_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "commission_rate_bp": 250
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The participation, with its commission recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "user_id": "11111111-0000-4000-8000-000000000001",
                    "role": "closer",
                    "commission_rate_bp": 250,
                    "commission_basis_clp": 1800000,
                    "commission_clp": 45000,
                    "created_at": "2026-09-14T11:30:00.000Z",
                    "updated_at": "2026-09-29T10:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations": {
      "get": {
        "summary": "List reservations — the active-holds worklist",
        "description": "The dealership’s HOLD REGISTER, most urgent first: ordered by `hold_expires_on` ascending, then by folio descending. Every status is included by default — a voided or completed reserva keeps its folio and stays listable so \"where is reserva R-47?\" has an answer (ADR 0058 §10) — so pass `status` to narrow to the live ones.\n\n**Each row carries its money.** `total_pagos_clp` is the sum of every ledger payment (`customer_payment` allocated via `payment_allocation`) recorded against the hold and `payment_count` is how many there are; a hold with no payments reports `0` / `0`. Both come from one grouped join, so the list is one query rather than one per row.\n\n**The saldo is NOT returned, deliberately.** It is `agreed_price_clp - total_pagos_clp`, and `agreed_price_clp` is nullable (`null` = \"not recorded\", which is not the same claim as `0`). Compute it client-side and render \"—\" when the price is absent; a server-side number would have to invent one of the two.\n\n`salesperson_id` is BR-312’s filter — \"which holds are mine?\". `vehicle_id` returns a unit’s hold HISTORY and answers nothing about whether that unit is available: `vehicle.status` via `/vehicles` is the only availability truth (ADR 0030).\n\nUnknown query parameters are a **400**, not a silent drop: a filter the caller believes is being applied, returning a full unfiltered list with a 200, is a worse answer than a refusal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "activa",
                "completada",
                "archivada",
                "anulada"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "salesperson_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "holder_contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The reservations, with payment totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "R-18",
                      "display_seq": 18,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                      "salesperson_id": "11111111-0000-4000-8000-000000000001",
                      "agreed_price_clp": 11900000,
                      "abono_expected_clp": 300000,
                      "taken_at": "2026-09-18T16:40:00.000Z",
                      "hold_expires_on": "2026-09-25",
                      "keep_advertised": true,
                      "transfer_cost_clp": 217740,
                      "transfer_cost_lines": [
                        {
                          "kind": "transferencia_municipal",
                          "label": "Impuesto a la transferencia (1,5 %)",
                          "amount_clp": 178500,
                          "fee_effective_from": "1979-12-29"
                        },
                        {
                          "kind": "arancel_registro_civil",
                          "label": "Inscripción en el Registro Civil",
                          "amount_clp": 39240,
                          "fee_effective_from": "2026-04-01"
                        }
                      ],
                      "status": "activa",
                      "abono_disposition": null,
                      "abono_disposition_reason": null,
                      "abono_disposition_at": null,
                      "abono_disposition_by": null,
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "created_at": "2026-09-18T16:40:00.000Z",
                      "updated_at": "2026-09-18T16:40:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Take a reservation",
        "description": "Takes a hold on a unit — the ONLY thing on this wave that locks inventory (a cotización creates no hold). Born `activa`; there is no draft state and no `status` field to supply.\n\n`vehicle_id` and `holder_contact_id` are required and must belong to the calling workspace; a foreign id and a nonexistent one answer identically (404), deliberately, because a difference between them is an existence oracle.\n\n`hold_expires_on` is REQUIRED and has no default (BR-241, corrected): unlike a cotización, a reserva locks the unit and can take a non-refundable deposit, so every hold states its own term rather than inheriting a silent policy number. `agreed_price_clp`, omitted, copies the vehicle's current asking price (BR-242) — that is a real default, not zero. `keep_advertised`, omitted, keeps the column default `true` (the marketplace ads stay live); an explicit `false` is the per-reserva opt-out that retracts them through the existing cierre queue, and it is set-once — there is no endpoint to change it on a standing hold.\n\n**Exactly one ACTIVE hold per unit.** A second `take` on a unit that already has one is a **409**, enforced by a partial unique index so a race loses too. Unknown body keys are a **400**. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "holder_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "salesperson_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "agreed_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "taken_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "hold_expires_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "keep_advertised": {
                    "type": "boolean"
                  },
                  "transfer_cost_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "transfer_cost_lines": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "transferencia_municipal",
                            "arancel_registro_civil",
                            "notarial",
                            "inscripcion",
                            "soap",
                            "tag"
                          ]
                        },
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "amount_clp": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "fee_effective_from": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                      },
                      "required": [
                        "kind",
                        "label",
                        "amount_clp"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 12
                  },
                  "abono_expected_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999999999999
                  }
                },
                "required": [
                  "vehicle_id",
                  "holder_contact_id",
                  "hold_expires_on"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                "salesperson_id": "11111111-0000-4000-8000-000000000001",
                "agreed_price_clp": 11900000,
                "hold_expires_on": "2026-09-25",
                "keep_advertised": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The reservation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "R-18",
                    "display_seq": 18,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "agreed_price_clp": 11900000,
                    "abono_expected_clp": 300000,
                    "taken_at": "2026-09-18T16:40:00.000Z",
                    "hold_expires_on": "2026-09-25",
                    "keep_advertised": true,
                    "transfer_cost_clp": 217740,
                    "transfer_cost_lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "status": "activa",
                    "abono_disposition": null,
                    "abono_disposition_reason": null,
                    "abono_disposition_at": null,
                    "abono_disposition_by": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-18T16:40:00.000Z",
                    "updated_at": "2026-09-18T16:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/reservations/{id}": {
      "get": {
        "summary": "Fetch one reservation, with its payments and totals",
        "description": "Accepts the UUID or the folio (`R-12`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The reservation, its payments and totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "R-18",
                    "display_seq": 18,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "agreed_price_clp": 11900000,
                    "abono_expected_clp": 300000,
                    "taken_at": "2026-09-18T16:40:00.000Z",
                    "hold_expires_on": "2026-09-25",
                    "keep_advertised": true,
                    "transfer_cost_clp": 217740,
                    "transfer_cost_lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "status": "activa",
                    "abono_disposition": null,
                    "abono_disposition_reason": null,
                    "abono_disposition_at": null,
                    "abono_disposition_by": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-18T16:40:00.000Z",
                    "updated_at": "2026-09-18T16:40:00.000Z",
                    "payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                        "sale_note_id": null,
                        "carried_from_reservation_id": null,
                        "instrument": "transferencia",
                        "amount_clp": 300000,
                        "paid_on": "2026-09-18",
                        "bank": "Banco Estado",
                        "account_number": null,
                        "document_number": null,
                        "note": "abono al tomar la reserva",
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "created_at": "2026-09-18T16:41:02.000Z"
                      }
                    ],
                    "totals": {
                      "total_a_pagar_clp": 300000,
                      "total_pagos_clp": 300000,
                      "saldo_clp": 0,
                      "recargo_tarjeta_clp": 0,
                      "total_a_cobrar_clp": 300000,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "converted_to_sale_note": null,
                    "holder": {
                      "id": "22222222-0000-4000-8000-000000000002",
                      "name": "Ana Pérez Soto",
                      "tax_id": "19312475-5",
                      "email": "ana.perez@example.com",
                      "phone": "+56987654321",
                      "address_street": "Av. Ejemplo",
                      "address_number": "1234",
                      "address_unit": "Depto 56",
                      "comuna_code": "13123",
                      "comuna_name": "Providencia"
                    },
                    "sends": [
                      {
                        "channel": "email",
                        "to": "ana.perez@example.com",
                        "sent_at": "2026-09-18T16:45:10.000Z",
                        "by": "11111111-0000-4000-8000-000000000001"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a live reservation",
        "description": "Changes what a LIVE hold may change after it was taken: the transfer cost (`transfer_cost_clp` alone = a manual amount and clears the breakdown), the term (`hold_expires_on`, never a past day — it also moves the seña’s deadline), the advertising decision (`keep_advertised`, through the existing cierre queue) and the salesperson. Never the price, the holder or the unit. **409** once the hold is not `activa`. Unknown keys are a 400. Answers the same shape as `GET /reservations/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "transfer_cost_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "transfer_cost_lines": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "transferencia_municipal",
                            "arancel_registro_civil",
                            "notarial",
                            "inscripcion",
                            "soap",
                            "tag"
                          ]
                        },
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "amount_clp": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 999999999999
                        },
                        "fee_effective_from": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                      },
                      "required": [
                        "kind",
                        "label",
                        "amount_clp"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 12
                  },
                  "hold_expires_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "keep_advertised": {
                    "type": "boolean"
                  },
                  "salesperson_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "transfer_cost_clp": 250000,
                "transfer_cost_lines": null,
                "hold_expires_on": "2026-09-30"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reservation, its payments, holder and sends",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "R-18",
                    "display_seq": 18,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "agreed_price_clp": 11900000,
                    "abono_expected_clp": 300000,
                    "taken_at": "2026-09-18T16:40:00.000Z",
                    "hold_expires_on": "2026-09-25",
                    "keep_advertised": true,
                    "transfer_cost_clp": 217740,
                    "transfer_cost_lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "status": "activa",
                    "abono_disposition": null,
                    "abono_disposition_reason": null,
                    "abono_disposition_at": null,
                    "abono_disposition_by": null,
                    "voided_at": null,
                    "void_reason": null,
                    "voided_by": null,
                    "created_at": "2026-09-18T16:40:00.000Z",
                    "updated_at": "2026-09-18T16:40:00.000Z",
                    "payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                        "sale_note_id": null,
                        "carried_from_reservation_id": null,
                        "instrument": "transferencia",
                        "amount_clp": 300000,
                        "paid_on": "2026-09-18",
                        "bank": "Banco Estado",
                        "account_number": null,
                        "document_number": null,
                        "note": "abono al tomar la reserva",
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "created_at": "2026-09-18T16:41:02.000Z"
                      }
                    ],
                    "totals": {
                      "total_a_pagar_clp": 300000,
                      "total_pagos_clp": 300000,
                      "saldo_clp": 0,
                      "recargo_tarjeta_clp": 0,
                      "total_a_cobrar_clp": 300000,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    },
                    "converted_to_sale_note": null,
                    "holder": {
                      "id": "22222222-0000-4000-8000-000000000002",
                      "name": "Ana Pérez Soto",
                      "tax_id": "19312475-5",
                      "email": "ana.perez@example.com",
                      "phone": "+56987654321",
                      "address_street": "Av. Ejemplo",
                      "address_number": "1234",
                      "address_unit": "Depto 56",
                      "comuna_code": "13123",
                      "comuna_name": "Providencia"
                    },
                    "sends": [
                      {
                        "channel": "email",
                        "to": "ana.perez@example.com",
                        "sent_at": "2026-09-18T16:45:10.000Z",
                        "by": "11111111-0000-4000-8000-000000000001"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations/transfer-cost-estimate": {
      "get": {
        "summary": "Estimate the transfer cost of a reserva",
        "description": "What the buyer would pay for the transfer of this unit at this price, today: the 1,5 % impuesto de transferencia (base = max(precio, tasación fiscal); the agreed price when the SII has no tasación for the model) plus the Registro Civil arancel in force. Computed by the statutory add-on machinery (`fee_schedule`), never a second fee table. Notaría is not included (no platform figure exists); `basis` says so. `agreed_price_clp` omitted = the vehicle’s asking price. Read-only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 999999999999
            },
            "required": false,
            "name": "agreed_price_clp",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The estimate: total, lines and a Spanish basis sentence",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total_clp": 217740,
                    "lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "basis": "Estimado: 1,5 % sobre el mayor valor entre el precio acordado y la tasación fiscal + arancel vigente del Registro Civil. No incluye notaría; ajusta el monto si corresponde.",
                    "agreed_price_clp": 11900000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations/{id}/pdf": {
      "get": {
        "summary": "Print the comprobante de reserva (PDF)",
        "description": "The workspace’s `reservation` template (else the platform starter) filled with the hold: folio, dates, dealership, holder (name, RUT, email, phone, address), unit, agreed price, abonos, saldo and transfer cost with its breakdown, signatures. RUT and domicilio print only for a caller that also holds `contacts:read` («—» otherwise). Never a 409: a fact not on record prints «—». Inline by default; `?download=1` answers as an attachment. Filename `reserva-<PATENTE>-<marca-modelo>.pdf` (e.g. `reserva-JLGJ43-mini-countryman-s.pdf`); the folio stands in for a missing plate, and `reserva-<folio>.pdf` when nothing is known.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "download",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations/{id}/send": {
      "post": {
        "summary": "Send the comprobante de reserva to the customer",
        "description": "Renders the current comprobante and sends it — always a person’s explicit act, never retried. `email`: the PDF attached, to the holder’s email or `to`. `whatsapp` (also needs `messages:send`): the PDF as a document in the holder’s WhatsApp conversation; **409** with `details.reason` = `whatsapp_window_closed` outside the 24 h window, `no_whatsapp_thread`, `no_phone`; `no_email` for email; Needs `contacts:read` too (the comprobante carries the holder’s RUT and address). `delivery_unconfirmed` when the provider errored (check the conversation before sending again). Each send is recorded and listed in `GET /reservations/{id}` → `sends`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "email",
                      "whatsapp"
                    ]
                  },
                  "to": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 320
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                },
                "required": [
                  "channel"
                ],
                "additionalProperties": false
              },
              "example": {
                "channel": "email"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was sent, to whom and when",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "channel": "email",
                    "to": "ana.perez@example.com",
                    "sent_at": "2026-09-18T16:45:10.000Z",
                    "message_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations/{id}/void": {
      "post": {
        "summary": "Void a reservation",
        "description": "A distinct `reservations:void` authority from `reservations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "void_reason": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "required": [
                  "void_reason"
                ]
              },
              "example": {
                "void_reason": "el cliente no completó el pago del abono a tiempo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The voided reservation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "R-22",
                    "display_seq": 22,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "agreed_price_clp": 11900000,
                    "abono_expected_clp": null,
                    "taken_at": "2026-09-18T16:40:00.000Z",
                    "hold_expires_on": "2026-09-25",
                    "keep_advertised": true,
                    "transfer_cost_clp": 217740,
                    "transfer_cost_lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "status": "anulada",
                    "abono_disposition": null,
                    "abono_disposition_reason": null,
                    "abono_disposition_at": null,
                    "abono_disposition_by": null,
                    "voided_at": "2026-09-20T10:05:00.000Z",
                    "void_reason": "el cliente no completó el pago del abono a tiempo",
                    "voided_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T16:40:00.000Z",
                    "updated_at": "2026-09-18T16:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/reservations/{id}/abono-disposition": {
      "post": {
        "summary": "Dispose of the abono on a reservation",
        "description": "A FOURTH distinct scope, `reservations:dispose_abono`, not OR’d with `:write`/`:void`. May also close a still-`activa` hold in the same act.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "abono_disposition": {
                    "type": "string",
                    "enum": [
                      "aplicado",
                      "devuelto",
                      "perdido"
                    ]
                  },
                  "abono_disposition_reason": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "void_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "abono_disposition",
                  "abono_disposition_reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "abono_disposition": "devuelto",
                "abono_disposition_reason": "el cliente desistió; se devolvió el abono"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reservation after disposition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_id": "R-18",
                    "display_seq": 18,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                    "salesperson_id": "11111111-0000-4000-8000-000000000001",
                    "agreed_price_clp": 11900000,
                    "abono_expected_clp": 300000,
                    "taken_at": "2026-09-18T16:40:00.000Z",
                    "hold_expires_on": "2026-09-25",
                    "keep_advertised": true,
                    "transfer_cost_clp": 217740,
                    "transfer_cost_lines": [
                      {
                        "kind": "transferencia_municipal",
                        "label": "Impuesto a la transferencia (1,5 %)",
                        "amount_clp": 178500,
                        "fee_effective_from": "1979-12-29"
                      },
                      {
                        "kind": "arancel_registro_civil",
                        "label": "Inscripción en el Registro Civil",
                        "amount_clp": 39240,
                        "fee_effective_from": "2026-04-01"
                      }
                    ],
                    "status": "anulada",
                    "abono_disposition": "devuelto",
                    "abono_disposition_reason": "el cliente desistió; se devolvió el abono",
                    "abono_disposition_at": "2026-09-21T09:00:00.000Z",
                    "abono_disposition_by": "11111111-0000-4000-8000-000000000001",
                    "voided_at": "2026-09-21T09:00:00.000Z",
                    "void_reason": "cierre por disposición del abono",
                    "voided_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-18T16:40:00.000Z",
                    "updated_at": "2026-09-18T16:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservations/{id}/convert": {
      "post": {
        "summary": "Convert a reservation into a nota de venta",
        "description": "Produces a NEW sale-note document — 201, the same as `POST /sale-notes`. Scoped `sale_notes:write`, borrowed rather than a fifth reservations scope.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Reservations"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "seller_of_record": {
                    "type": "string",
                    "enum": [
                      "consignante",
                      "retoma",
                      "automotora"
                    ]
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado",
                      "pass_through"
                    ]
                  },
                  "tax_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "issued_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "lead_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^L-\\d+$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "carry_transfer_cost": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "tax_treatment"
                ],
                "additionalProperties": false
              },
              "example": {
                "seller_of_record": "automotora",
                "tax_treatment": "afecto",
                "tax_clp": 1900000,
                "issued_at": "2026-09-24T12:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The reservation, the new sale note, and carried payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "reservation": {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "R-18",
                      "display_seq": 18,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "holder_contact_id": "22222222-0000-4000-8000-000000000002",
                      "salesperson_id": "11111111-0000-4000-8000-000000000001",
                      "agreed_price_clp": 11900000,
                      "abono_expected_clp": 300000,
                      "taken_at": "2026-09-18T16:40:00.000Z",
                      "hold_expires_on": "2026-09-25",
                      "keep_advertised": true,
                      "transfer_cost_clp": 217740,
                      "transfer_cost_lines": [
                        {
                          "kind": "transferencia_municipal",
                          "label": "Impuesto a la transferencia (1,5 %)",
                          "amount_clp": 178500,
                          "fee_effective_from": "1979-12-29"
                        },
                        {
                          "kind": "arancel_registro_civil",
                          "label": "Inscripción en el Registro Civil",
                          "amount_clp": 39240,
                          "fee_effective_from": "2026-04-01"
                        }
                      ],
                      "status": "completada",
                      "abono_disposition": "aplicado",
                      "abono_disposition_reason": null,
                      "abono_disposition_at": "2026-09-24T12:00:00.000Z",
                      "abono_disposition_by": "11111111-0000-4000-8000-000000000001",
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "created_at": "2026-09-18T16:40:00.000Z",
                      "updated_at": "2026-09-18T16:40:00.000Z"
                    },
                    "sale_note": {
                      "id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_id": "V-118",
                      "display_seq": 118,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                      "buyer_contact_id": "22222222-0000-4000-8000-000000000002",
                      "lead_id": null,
                      "converted_from_reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "seller_of_record": "automotora",
                      "salesperson_id": "11111111-0000-4000-8000-000000000001",
                      "net_clp": 11900000,
                      "list_price_clp": 12490000,
                      "discount_clp": 590000,
                      "tax_clp": 1900000,
                      "tax_treatment": "afecto",
                      "status": "issued",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "emitted_by": "11111111-0000-4000-8000-000000000001",
                      "approved_by": null,
                      "approved_at": null,
                      "voided_at": null,
                      "void_reason": null,
                      "voided_by": null,
                      "created_at": "2026-09-19T13:00:00.000Z",
                      "updated_at": "2026-09-19T13:00:00.000Z"
                    },
                    "carried_payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": null,
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "carried_from_reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                        "instrument": "transferencia",
                        "amount_clp": 300000,
                        "paid_on": "2026-09-18",
                        "bank": "Banco Estado",
                        "account_number": null,
                        "document_number": null,
                        "note": "abono al tomar la reserva",
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "created_at": "2026-09-18T16:41:02.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/document-payments": {
      "get": {
        "summary": "DEPRECATED — the payments and running totals for one document",
        "deprecated": true,
        "description": "The query must name exactly one of `reservation_id` / `sale_note_id`. Deliberately no fetch-by-id route — a payment is only ever read in the context of the document it belongs to.\n\n**DEPRECATED (payments ledger T11).** The rows served here are now the ALLOCATIONS against the document’s obligation, projected into this endpoint’s existing shape; `id` is the payment’s id and `amount_clp` is what that payment settles ON THIS DOCUMENT. The totals are unchanged and still come from the one roll-up (BR-249). `GET /obligations/{id}` and `GET /contacts/{id}/ledger` are the replacements.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Document Payments"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "reservation_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "sale_note_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Payments and totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "payments": [
                      {
                        "id": "d6d6d6d6-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "reservation_id": null,
                        "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "carried_from_reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                        "instrument": "tarjeta_credito",
                        "amount_clp": 8900000,
                        "paid_on": "2026-09-24",
                        "bank": null,
                        "account_number": null,
                        "document_number": "482910",
                        "note": null,
                        "card_fee_bps": 250,
                        "card_surcharge_clp": 222500,
                        "card_surcharge_source": "calculado",
                        "created_at": "2026-09-24T12:05:00.000Z"
                      }
                    ],
                    "totals": {
                      "total_a_pagar_clp": 8900000,
                      "total_pagos_clp": 8900000,
                      "saldo_clp": 0,
                      "recargo_tarjeta_clp": 222500,
                      "total_a_cobrar_clp": 9122500,
                      "payment_count": 1,
                      "complete": true,
                      "gaps": []
                    }
                  },
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "DEPRECATED — record a payment against a reservation or a sale note",
        "deprecated": true,
        "description": "The body must name exactly one of `reservation_id` / `sale_note_id` (UUID or folio).\n\n**DEPRECATED (payments ledger T11).** This endpoint no longer writes `document_payment`: it records a `customer_payment` and allocates it to the document's `customer_obligation`, so there is exactly one answer to \"how much has this customer paid us\". It is kept because the ERP screens call it daily, and its request and response shapes are unchanged. **Use `POST /payments`** for anything new — it can split one payment across several obligations, leave a surplus as the contact’s credit, accept an `Idempotency-Key`, and be reversed, none of which this door can express. `Idempotency-Key` IS honoured here too.\n\n⚠ `amount_clp` in the response is WHAT LANDED ON THE DOCUMENT (the allocation), which equals the amount recorded on every path that had an obligation to settle, and is `0` when the document had none (no holder contact, or nothing owed) — the payment itself is still the full amount and is readable on the contact’s ficha through `GET /payments`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Document Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reservation_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^R-\\d+$"
                      }
                    ]
                  },
                  "sale_note_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^V-\\d+$"
                      }
                    ]
                  },
                  "instrument": {
                    "type": "string",
                    "enum": [
                      "efectivo",
                      "transferencia",
                      "cheque",
                      "vale_vista",
                      "tarjeta",
                      "tarjeta_debito",
                      "tarjeta_credito",
                      "otro"
                    ]
                  },
                  "amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "paid_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "card_surcharge_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "bank": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "account_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "document_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "instrument",
                  "amount_clp",
                  "paid_on"
                ],
                "additionalProperties": false
              },
              "example": {
                "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                "instrument": "transferencia",
                "amount_clp": 300000,
                "paid_on": "2026-09-18",
                "bank": "Banco Estado",
                "note": "abono al tomar la reserva"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d6d6d6d6-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "reservation_id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "sale_note_id": null,
                    "carried_from_reservation_id": null,
                    "instrument": "transferencia",
                    "amount_clp": 300000,
                    "paid_on": "2026-09-18",
                    "bank": "Banco Estado",
                    "account_number": null,
                    "document_number": null,
                    "note": "abono al tomar la reserva",
                    "card_fee_bps": null,
                    "card_surcharge_clp": null,
                    "card_surcharge_source": null,
                    "created_at": "2026-09-18T16:41:02.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/price-approvals": {
      "get": {
        "summary": "The approval queue",
        "description": "Oldest-first. Cost fields (e.g. `floor_price_snapshot_clp`) are ABSENT from the response unless the caller also holds `dealership_economics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Price Approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "approved",
                "rejected"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "quote",
                "reservation",
                "sale_note"
              ]
            },
            "required": false,
            "name": "subject_kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": false,
            "name": "subject_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Price approvals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dbdbdbdb-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "quote_id": null,
                      "reservation_id": null,
                      "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "requested_price_clp": 10900000,
                      "list_price_snapshot_clp": 11900000,
                      "floor_price_snapshot_clp": 11200000,
                      "requester_user_id": "11111111-0000-4000-8000-000000000002",
                      "status": "pending",
                      "decider_user_id": null,
                      "decided_at": null,
                      "decision_note": null,
                      "created_at": "2026-09-19T12:40:00.000Z",
                      "updated_at": "2026-09-19T12:40:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Request approval for a below-reference price",
        "description": "Names exactly one of `quote_id` / `reservation_id` / `sale_note_id`. `requested_price_clp` is the price the buyer pays for the car — IVA included on an afecto sale — the same terms as the published price and the precio piso it is measured against (ADR 0114 §2.2). A sale note is approved when what its buyer pays is at or above an approved request. The requester is the authenticated principal, never a body field.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Price Approvals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quote_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^Q-\\d+$"
                      }
                    ]
                  },
                  "reservation_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^R-\\d+$"
                      }
                    ]
                  },
                  "sale_note_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "pattern": "^V-\\d+$"
                      }
                    ]
                  },
                  "requested_price_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  }
                },
                "required": [
                  "requested_price_clp"
                ],
                "additionalProperties": false
              },
              "example": {
                "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                "requested_price_clp": 10900000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The requested approval",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "dbdbdbdb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "quote_id": null,
                    "reservation_id": null,
                    "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "requested_price_clp": 10900000,
                    "list_price_snapshot_clp": 11900000,
                    "floor_price_snapshot_clp": 11200000,
                    "requester_user_id": "11111111-0000-4000-8000-000000000002",
                    "status": "pending",
                    "decider_user_id": null,
                    "decided_at": null,
                    "decision_note": null,
                    "created_at": "2026-09-19T12:40:00.000Z",
                    "updated_at": "2026-09-19T12:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/price-approvals/evaluate": {
      "get": {
        "summary": "Would this price need approval?",
        "description": "Evaluates `price_clp` — what the buyer pays for the car, IVA included on an afecto sale, the same terms as the published price and the precio piso (ADR 0114 §2.2) — against the unit named by exactly one of `vehicle_id` / `quote_id` / `reservation_id` / `sale_note_id` (UUID or folio), with the same policy `POST /price-approvals` enforces — so a screen that hides «Pedir aprobación» on `needs_approval: false` hides exactly the request the POST would refuse. `reason` is the stable code (`below_reference` | `within_tolerance` | `at_or_above_reference` | `no_reference`); `message` is a ready-to-show Spanish sentence. When the reference is the unit floor, `reference_clp` and `shortfall_clp` are ABSENT unless the caller holds `dealership_economics:read`. Requires `price_approval:read` or `price_approval:request`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Price Approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^Q-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "quote_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^R-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "reservation_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^V-\\d+$"
                }
              ]
            },
            "required": false,
            "name": "sale_note_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,12}$"
            },
            "required": true,
            "name": "price_clp",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The verdict",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "needs_approval": true,
                    "reason": "below_reference",
                    "message": "El precio está $500.000 bajo el precio publicado ($12.990.000). Requiere la aprobación de otra persona.",
                    "reference_kind": "list",
                    "reference_clp": 12990000,
                    "shortfall_clp": 500000,
                    "tolerance_clp": 0,
                    "vehicle_id": "5b0c7a7e-2f3d-4c1e-9a4b-7d2e8f1c3a60"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-approvals/{id}": {
      "get": {
        "summary": "Fetch one price approval",
        "description": "The approval as stored, whatever its `status` (`pending` | `approved` | `rejected`). Cost fields (`list_price_snapshot_clp`, `floor_price_snapshot_clp`) are ABSENT from the response — never `null` — unless the caller also holds `dealership_economics:read`, the same gate `GET /price-approvals` applies. An approval belonging to another workspace is a 404, indistinguishable from one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Price Approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The price approval",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dbdbdbdb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "quote_id": null,
                    "reservation_id": null,
                    "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "requested_price_clp": 10900000,
                    "list_price_snapshot_clp": 11900000,
                    "floor_price_snapshot_clp": 11200000,
                    "requester_user_id": "11111111-0000-4000-8000-000000000002",
                    "status": "pending",
                    "decider_user_id": null,
                    "decided_at": null,
                    "decision_note": null,
                    "created_at": "2026-09-19T12:40:00.000Z",
                    "updated_at": "2026-09-19T12:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-approvals/{id}/decision": {
      "post": {
        "summary": "Decide a price approval",
        "description": "Terminal — no PUT/PATCH and no second decision. `price_approval:approve` is a single, non-OR’d scope: holding the requesting scope never grants the deciding authority.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Price Approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "decision_note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "decision"
                ],
                "additionalProperties": false
              },
              "example": {
                "decision": "approved",
                "decision_note": "autorizado por gerencia dado el tiempo en stock"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decided approval",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dbdbdbdb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "quote_id": null,
                    "reservation_id": null,
                    "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                    "requested_price_clp": 10900000,
                    "list_price_snapshot_clp": 11900000,
                    "floor_price_snapshot_clp": 11200000,
                    "requester_user_id": "11111111-0000-4000-8000-000000000002",
                    "status": "approved",
                    "decider_user_id": "11111111-0000-4000-8000-000000000001",
                    "decided_at": "2026-09-19T16:05:00.000Z",
                    "decision_note": "autorizado por gerencia dado el tiempo en stock",
                    "created_at": "2026-09-19T12:40:00.000Z",
                    "updated_at": "2026-09-19T12:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/kanban": {
      "get": {
        "summary": "The whole reconditioning board for one vehicle pipeline",
        "description": "Every column in position order, each with the cars on it. A card is a MINIMAL projection (make/model/version/year, título, patente, one photo, `photos_count`, the ADR-0030 commercial `status`, and `stage_entered_at`) and deliberately carries NO pricing and no cost. Each column carries `sla_days` — the per-stage ageing threshold in days, `null` for no limit — which combined with `stage_entered_at` is what makes a card \"atrasado\"; the ageing itself is derived, never stored. Whole-board fetch capped at 500 cars total, and `meta.capped` says whether the cap was hit. A pipeline whose kind is not `vehicle` is a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "pipeline_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The board",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "stages": [
                      {
                        "id": "55555555-0000-4000-8000-000000000001",
                        "name": "Recepción",
                        "slug": "reception",
                        "position": 0,
                        "category": "open",
                        "is_terminal": false,
                        "description": "El vehículo ya ingresó físicamente.",
                        "sla_days": 1,
                        "vehicles": []
                      },
                      {
                        "id": "55555555-0000-4000-8000-000000000002",
                        "name": "Peritaje",
                        "slug": "appraisal",
                        "position": 1,
                        "category": "open",
                        "is_terminal": false,
                        "description": "Revisión mecánica y estructural.",
                        "sla_days": 2,
                        "vehicles": [
                          {
                            "id": "e1e1e1e1-0000-4000-8000-000000000001",
                            "make": "Chevrolet",
                            "model": "Sail",
                            "version": null,
                            "year": 2020,
                            "title": null,
                            "registration_number": null,
                            "photo_url": null,
                            "photos_count": 0,
                            "status": "disponible",
                            "stage_entered_at": "2026-09-20T01:46:34.125Z"
                          }
                        ]
                      }
                    ]
                  },
                  "meta": {
                    "total": 1,
                    "limit": 500,
                    "capped": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/pipeline": {
      "put": {
        "summary": "Place a vehicle on a reconditioning board, move boards, or remove it",
        "description": "Places the car on a `kind=vehicle` pipeline, landing on that board's ENTRY column (lowest position, category `open`) unless `stage_id` names another column OF THAT SAME BOARD. A car already placed is RE-placed and the change is recorded in `stage_transition_log`. ⚠ `pipeline_id: null` TAKES THE CAR OFF ITS BOARD AND DELETES NOTHING — the car, its history and its past transitions all remain; only the placement is cleared. Placement is OPTIONAL by design: most cars are on no board at all, and that is not an error state. A pipeline of another kind is a 400; an unknown or foreign one is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pipeline_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "pipeline_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "pipeline_id": "44444444-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The vehicle placement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000001",
                    "stage_entered_at": "2026-09-20T01:46:33.764Z",
                    "status": "disponible",
                    "stage_blocked_reason": null,
                    "prep_assignee_user_id": null,
                    "registration_number": null,
                    "title": null
                  },
                  "meta": {
                    "outcome": "moved"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/stage": {
      "put": {
        "summary": "Move a vehicle to another column of its board",
        "description": "The card move. One transaction: the row is locked, the target is checked against the current column's transition whitelist (`allowed_transitions_to`; empty = unrestricted), the move lands in `stage_transition_log` with the actor, and `stage_entered_at` is re-stamped — except on a no-op re-drop onto the same column, which deliberately does NOT reset the ageing clock. ⚠ IT NEVER WRITES `status`, `reserved_at` OR `sold_at`: the operational stage and the commercial status (ADR 0030) are orthogonal, so a car in Mecánica may be reservado and a car on the terminal column may already be vendido. A car on NO board is a 409 (\"place it first\") and never an implicit placement; a stage belonging to another board is a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stage_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "stage_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "stage_id": "55555555-0000-4000-8000-000000000002",
                "reason": "Perito revisó el auto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The vehicle placement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000002",
                    "stage_entered_at": "2026-09-20T01:46:34.125Z",
                    "status": "disponible",
                    "stage_blocked_reason": null,
                    "prep_assignee_user_id": null,
                    "registration_number": null,
                    "title": null
                  },
                  "meta": {
                    "outcome": "moved"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/stage-block": {
      "put": {
        "summary": "Record (or clear) why a vehicle is stuck in its current column",
        "description": "Sets the short label a board card shows when a car cannot progress (\"esperando repuesto de Japón\"). `reason: null` CLEARS it. ⚠ THE BLOCK IS SCOPED TO THE CURRENT COLUMN AND A STAGE MOVE CLEARS IT: it explains why the car is stuck HERE, so carrying it into the next column would claim the car is still waiting on a part it already received. A no-op re-drop onto the same column writes nothing and therefore clears nothing — this endpoint with a null reason is the verb for un-blocking. Capped at 120 characters because it renders ON A CARD; the move `reason` (500) is the place for a sentence, and it lands in `stage_transition_log` forever. Blank text is a 400. A car that is on NO board is a 409 — a block names the column the car is stuck in, and there is none. ⚠ IT NEVER WRITES `status`, `reserved_at` OR `sold_at`: a blocked car is not thereby unavailable (ADR 0030), and the body is `.strict()`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Esperando repuesto de importación"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The vehicle placement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000002",
                    "stage_entered_at": "2026-09-20T01:46:34.125Z",
                    "status": "disponible",
                    "stage_blocked_reason": "Esperando repuesto de importación",
                    "prep_assignee_user_id": null,
                    "registration_number": null,
                    "title": null
                  },
                  "meta": {
                    "outcome": "moved"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/prep-assignee": {
      "put": {
        "summary": "Name (or un-name) the person driving a vehicle's preparación",
        "description": "Sets who owns getting this unit out of the column it is on. `user_id: null` un-assigns. The value is a RAW user id and every read returns it as one — the display name is resolved CLIENT-SIDE against `GET /memberships`, this platform's convention wherever a person appears on a card, because the column carries no FK (it outlives a membership delete). ⚠ ANY WELL-FORMED uuid IS ACCEPTED AND MEMBERSHIP IS NOT VALIDATED — deliberately identical to `lead.owner_user_id` and `ticket.assignee_user_id`, and recorded as a known cross-entity gap rather than a rule invented for one noun. UNLIKE THE BLOCK, THIS SURVIVES A STAGE MOVE: the person driving the unit does not change because it reached the next column. A car that is on NO board is a 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "user_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "user_id": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The vehicle placement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "pipeline_id": "44444444-0000-4000-8000-000000000001",
                    "stage_id": "55555555-0000-4000-8000-000000000002",
                    "stage_entered_at": "2026-09-20T01:46:34.125Z",
                    "status": "disponible",
                    "stage_blocked_reason": "Esperando repuesto de importación",
                    "prep_assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "registration_number": null,
                    "title": null
                  },
                  "meta": {
                    "outcome": "moved"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicle-attachments": {
      "get": {
        "summary": "List the car's papers",
        "description": "`vehicle_id` is required; there is no tenant-wide list of documents. Newest first. Each paper carries its `kind`, its `label` (an `otro` paper) and its `expires_on` («Vence»), when recorded.\n\n`expiring_within_days` (ADR 0114 §7) narrows to the papers «Vencido» or due within that many days, soonest first, each with `expiry_state`; without `vehicle_id` it covers every car in stock and never returns a cédula or a paper naming a person. The same papers raise the `vehicle_papers_expiring` dealer alert and fill the Stock counts `papers_expired` / `papers_expiring` on `GET /vehicles/stats`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The car. Required unless `expiring_within_days` is sent; with it, omit `vehicle_id` to list the due papers of every car in stock."
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "padron",
                "cedula",
                "contrato",
                "certificado_anotaciones",
                "factura",
                "permiso_circulacion",
                "revision_tecnica",
                "soap",
                "otro"
              ],
              "description": "Which paper: `padron` (padrón / certificado de inscripción), `certificado_anotaciones` (certificado de anotaciones vigentes, CAV), `permiso_circulacion` (permiso de circulación), `revision_tecnica` (revisión técnica), `soap` (SOAP), `factura`, `contrato` (contrato firmado), `cedula` (cédula de identidad del dueño o consignante) or `otro` (any other paper, named by `label`)."
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "description": "Only the papers that are «Vencido» or vence within this many days (0 = vencidos and those that vence today), soonest first, each with `expiry_state` (`expired` | `expiring`). A paper replaced by a later one of the same kind (or the same name, for `otro`) is not listed. Without `vehicle_id`: every car in stock (active, not vendido); cédulas and papers naming a person are never included. The Stock facets use 30.",
              "example": 30
            },
            "required": false,
            "name": "expiring_within_days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Vehicle attachments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "f1f1f1f1-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "padron",
                      "storage_bucket": "vitrina-registry",
                      "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/padron.png",
                      "filename": "padron.png",
                      "mime_type": "image/png",
                      "byte_size": 812004,
                      "subject_contact_id": null,
                      "uploaded_by": "11111111-0000-4000-8000-000000000001",
                      "uploaded_at": "2026-09-15T18:47:01.784Z",
                      "created_at": "2026-09-15T18:47:01.784Z",
                      "signed_document_type": null,
                      "signed_document_id": null,
                      "label": null,
                      "expires_on": null
                    },
                    {
                      "id": "f1f1f1f1-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "cedula",
                      "storage_bucket": "vitrina-registry",
                      "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/cedula-consignante.png",
                      "filename": "cedula-consignante.png",
                      "mime_type": "image/png",
                      "byte_size": 812004,
                      "subject_contact_id": "22222222-0000-4000-8000-000000000001",
                      "uploaded_by": "11111111-0000-4000-8000-000000000001",
                      "uploaded_at": "2026-09-15T18:47:01.784Z",
                      "created_at": "2026-09-15T18:47:01.784Z",
                      "signed_document_type": null,
                      "signed_document_id": null,
                      "label": null,
                      "expires_on": null
                    },
                    {
                      "id": "f1f1f1f1-0000-4000-8000-000000000003",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "contrato",
                      "storage_bucket": "vitrina-registry",
                      "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/compraventa-firmada.pdf",
                      "filename": "compraventa-firmada.pdf",
                      "mime_type": "application/pdf",
                      "byte_size": 812004,
                      "subject_contact_id": null,
                      "uploaded_by": "11111111-0000-4000-8000-000000000001",
                      "uploaded_at": "2026-09-15T18:47:01.784Z",
                      "created_at": "2026-09-15T18:47:01.784Z",
                      "signed_document_type": "compraventa",
                      "signed_document_id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "label": null,
                      "expires_on": null
                    },
                    {
                      "id": "f1f1f1f1-0000-4000-8000-000000000004",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "soap",
                      "storage_bucket": "vitrina-registry",
                      "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/soap-2027.jpg",
                      "filename": "soap-2027.jpg",
                      "mime_type": "image/jpeg",
                      "byte_size": 812004,
                      "subject_contact_id": null,
                      "uploaded_by": "11111111-0000-4000-8000-000000000001",
                      "uploaded_at": "2026-09-15T18:47:01.784Z",
                      "created_at": "2026-09-15T18:47:01.784Z",
                      "signed_document_type": null,
                      "signed_document_id": null,
                      "label": null,
                      "expires_on": "2027-03-31"
                    },
                    {
                      "id": "f1f1f1f1-0000-4000-8000-000000000005",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "kind": "otro",
                      "storage_bucket": "vitrina-registry",
                      "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/informe.pdf",
                      "filename": "informe.pdf",
                      "mime_type": "application/pdf",
                      "byte_size": 812004,
                      "subject_contact_id": null,
                      "uploaded_by": "11111111-0000-4000-8000-000000000001",
                      "uploaded_at": "2026-09-15T18:47:01.784Z",
                      "created_at": "2026-09-15T18:47:01.784Z",
                      "signed_document_type": null,
                      "signed_document_id": null,
                      "label": "Informe mecánico",
                      "expires_on": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "File one of the car's papers (multipart/form-data)",
        "description": "Capped at the configured max size; the file part is required. PDF or an image (JPEG, PNG, WebP, HEIC, TIFF — a phone photo of a paper is fine).\n\nTHE CAR’S PAPERS (ADR 0114 §7): `kind` names the paper — `permiso_circulacion`, `revision_tecnica`, `soap`, `padron`, `certificado_anotaciones` (CAV), `factura`, `contrato`, `cedula`. Any other paper is `otro` with a `label` that names it («Factura de compra», «Informe mecánico»); a `label` with no `kind` files it as `otro`. `expires_on` records the day the paper stops being valid («Vence»). `replaces_id` supersedes a filed paper of the same kind: the old one is erased once the new one is filed.\n\nA SIGNED COPY of one of the car’s documents (ADR 0114 §7) is filed with `signed_document_type` (`purchase_note`, `sale_note`, `compraventa`, `consignment_contract`, `liquidacion`) and `signed_document_id` (the nota de venta’s id for a compraventa, the consignación’s id for a liquidación). The document must be this car’s, and a compraventa must have been printed; `kind` may then be omitted — it is `contrato` for the compraventa and the mandato, `otro` for the rest — and a contradicting `kind` is refused (400). `GET /vehicles/{id}/documents` shows the copy on its document.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "padron",
                      "cedula",
                      "contrato",
                      "certificado_anotaciones",
                      "factura",
                      "permiso_circulacion",
                      "revision_tecnica",
                      "soap",
                      "otro"
                    ],
                    "description": "Which paper: `padron` (padrón / certificado de inscripción), `certificado_anotaciones` (certificado de anotaciones vigentes, CAV), `permiso_circulacion` (permiso de circulación), `revision_tecnica` (revisión técnica), `soap` (SOAP), `factura`, `contrato` (contrato firmado), `cedula` (cédula de identidad del dueño o consignante) or `otro` (any other paper, named by `label`)."
                  },
                  "subject_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "signed_document_type": {
                    "type": "string",
                    "enum": [
                      "purchase_note",
                      "sale_note",
                      "compraventa",
                      "consignment_contract",
                      "liquidacion"
                    ]
                  },
                  "signed_document_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The name of a paper filed as `otro` — «Factura de compra», «Informe mecánico». Only `otro` takes one; sending a label with no `kind` files the paper as `otro`.",
                    "example": "Informe mecánico"
                  },
                  "expires_on": {
                    "anyOf": [
                      {
                        "type": "string",
                        "description": "The day the paper stops being valid («Vence»), `YYYY-MM-DD`. The permiso de circulación, the revisión técnica and the SOAP expire; any paper may carry one.",
                        "example": "2027-03-31"
                      },
                      {
                        "type": "string",
                        "enum": [
                          ""
                        ]
                      }
                    ]
                  },
                  "replaces_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Replace a filed paper: the id of the paper this file supersedes (same car, same kind; not a signed copy). The old paper is erased only after the new one is filed."
                  },
                  "file": {}
                },
                "required": [
                  "vehicle_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "kind": "soap",
                "expires_on": "2027-03-31",
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "padron",
                      "cedula",
                      "contrato",
                      "certificado_anotaciones",
                      "factura",
                      "permiso_circulacion",
                      "revision_tecnica",
                      "soap",
                      "otro"
                    ],
                    "description": "Which paper: `padron` (padrón / certificado de inscripción), `certificado_anotaciones` (certificado de anotaciones vigentes, CAV), `permiso_circulacion` (permiso de circulación), `revision_tecnica` (revisión técnica), `soap` (SOAP), `factura`, `contrato` (contrato firmado), `cedula` (cédula de identidad del dueño o consignante) or `otro` (any other paper, named by `label`)."
                  },
                  "subject_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "signed_document_type": {
                    "type": "string",
                    "enum": [
                      "purchase_note",
                      "sale_note",
                      "compraventa",
                      "consignment_contract",
                      "liquidacion"
                    ]
                  },
                  "signed_document_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The name of a paper filed as `otro` — «Factura de compra», «Informe mecánico». Only `otro` takes one; sending a label with no `kind` files the paper as `otro`.",
                    "example": "Informe mecánico"
                  },
                  "expires_on": {
                    "anyOf": [
                      {
                        "type": "string",
                        "description": "The day the paper stops being valid («Vence»), `YYYY-MM-DD`. The permiso de circulación, the revisión técnica and the SOAP expire; any paper may carry one.",
                        "example": "2027-03-31"
                      },
                      {
                        "type": "string",
                        "enum": [
                          ""
                        ]
                      }
                    ]
                  },
                  "replaces_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Replace a filed paper: the id of the paper this file supersedes (same car, same kind; not a signed copy). The old paper is erased only after the new one is filed."
                  }
                },
                "required": [
                  "filename",
                  "content",
                  "vehicle_id"
                ]
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "kind": "soap",
                "expires_on": "2027-03-31",
                "filename": "permiso-circulacion.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The uploaded attachment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "f1f1f1f1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "kind": "padron",
                    "storage_bucket": "vitrina-registry",
                    "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/padron.png",
                    "filename": "padron.png",
                    "mime_type": "image/png",
                    "byte_size": 812004,
                    "subject_contact_id": null,
                    "uploaded_by": "11111111-0000-4000-8000-000000000001",
                    "uploaded_at": "2026-09-15T18:47:01.784Z",
                    "created_at": "2026-09-15T18:47:01.784Z",
                    "signed_document_type": null,
                    "signed_document_id": null,
                    "label": null,
                    "expires_on": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicle-attachments/{id}/content": {
      "get": {
        "summary": "Download the stored bytes of one attachment",
        "description": "Streams the raw file (not a JSON envelope) with `Cache-Control: private, no-store` and a locked-down CSP. Audited on every read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The stored file bytes",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicle-attachments/{id}": {
      "delete": {
        "summary": "Delete one of the car's papers",
        "description": "Erases the storage object first, then the row. A paper still cited by a transferencia or a document discrepancy is refused (409) before anything is touched. Recorded in the car’s Historial.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Rename a paper or set its «Vence» day",
        "description": "ADR 0114 §7 — corrects a filed paper without touching its file: `label` renames a paper filed as `otro` (a named kind — SOAP, padrón… — is named by its kind and refuses a label, 400); `expires_on` sets the day the paper stops being valid, `null` clears it. A new file is a replace (`POST` with `replaces_id`), not this. Recorded in the car’s Historial.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicle Attachments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120,
                    "description": "The name of a paper filed as `otro` — «Factura de compra», «Informe mecánico». Only `otro` takes one; sending a label with no `kind` files the paper as `otro`.",
                    "example": "Informe mecánico"
                  },
                  "expires_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The day the paper stops being valid («Vence»), `YYYY-MM-DD`; `null` clears it.",
                    "example": "2027-03-31"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "expires_on": "2027-03-31"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated paper",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "f1f1f1f1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "kind": "soap",
                    "storage_bucket": "vitrina-registry",
                    "storage_path": "a1a1a1a1-0000-4000-8000-000000000001/e1e1e1e1-0000-4000-8000-000000000001/padron.png",
                    "filename": "padron.png",
                    "mime_type": "image/png",
                    "byte_size": 812004,
                    "subject_contact_id": null,
                    "uploaded_by": "11111111-0000-4000-8000-000000000001",
                    "uploaded_at": "2026-09-15T18:47:01.784Z",
                    "created_at": "2026-09-15T18:47:01.784Z",
                    "signed_document_type": null,
                    "signed_document_id": null,
                    "label": null,
                    "expires_on": "2027-03-31"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/instagram-posts/vehicles/{vehicleId}/drafts": {
      "post": {
        "summary": "Prepare an Instagram post for one-click approval",
        "description": "What choosing Instagram in a vehicle's \"Publicar\" step does: prepares a post in `pending_approval` (server-generated caption, the first 10 photos of the gallery) and publishes NOTHING. 201 when created; 200 with the existing post when the vehicle already has one waiting. 422 when the vehicle has no photos or no Instagram account is connected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Instagram Posts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "vehicleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post already waiting for approval",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "The prepared post",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "1a5ea0c1-0000-4000-8000-000000000001",
                    "vehicle_id": "c4c4c4c4-0000-4000-8000-000000000001",
                    "status": "pending_approval",
                    "caption": "⚡️Se vende! Toyota RAV4 Híbrido 2022\n\n$24.990.000\n\n- Automática\n- 38.000 km\n\nEscríbenos por DM.",
                    "photos": [
                      {
                        "photo_index": 0,
                        "crop": null,
                        "resolved_url": "https://cdn.vitrinadev.com/vehicles/rav4-1.jpg"
                      },
                      {
                        "photo_index": 1,
                        "crop": null,
                        "resolved_url": "https://cdn.vitrinadev.com/vehicles/rav4-2.jpg"
                      }
                    ],
                    "scheduled_at": null,
                    "published_at": null,
                    "published_media_id": null,
                    "messaging_account_id": null,
                    "error": null,
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-10-15T14:00:00.000Z",
                    "updated_at": "2026-10-15T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/instagram-posts/{id}/approve": {
      "post": {
        "summary": "Approve and publish (or schedule) a prepared Instagram post",
        "description": "Publishes a `pending_approval` post, optionally with an edited caption / photo selection, or schedules it when `scheduled_at` is present. Like the compose POST, a publish that Instagram refuses answers 200 with `status: \"failed\"` and the reason — branch on `status`. 409 when the post is no longer waiting for approval.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Instagram Posts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "photo_index": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "crop": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "preset": {
                              "type": "string",
                              "maxLength": 50
                            },
                            "x": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "y": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "width": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "height": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "required": [
                        "photo_index"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "caption": {
                    "type": "string",
                    "maxLength": 2200
                  },
                  "scheduled_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "caption": "⚡️Se vende! Toyota RAV4 Híbrido 2022\n\n$24.990.000\n\n- Automática\n- 38.000 km\n\nEscríbenos por DM.",
                "scheduled_at": "2026-10-16T13:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The approved post",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "1a5ea0c1-0000-4000-8000-000000000001",
                    "vehicle_id": "c4c4c4c4-0000-4000-8000-000000000001",
                    "status": "scheduled",
                    "caption": "⚡️Se vende! Toyota RAV4 Híbrido 2022\n\n$24.990.000\n\n- Automática\n- 38.000 km\n\nEscríbenos por DM.",
                    "photos": [
                      {
                        "photo_index": 0,
                        "crop": null,
                        "resolved_url": "https://cdn.vitrinadev.com/vehicles/rav4-1.jpg"
                      },
                      {
                        "photo_index": 1,
                        "crop": null,
                        "resolved_url": "https://cdn.vitrinadev.com/vehicles/rav4-2.jpg"
                      }
                    ],
                    "scheduled_at": "2026-10-16T13:00:00.000Z",
                    "published_at": null,
                    "published_media_id": null,
                    "messaging_account_id": null,
                    "error": null,
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-10-15T14:00:00.000Z",
                    "updated_at": "2026-10-15T14:02:10.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/margin/preview": {
      "post": {
        "summary": "Preview the margin of a nota de venta being written",
        "description": "The margin a nota de venta WOULD leave if the unit sold today at `net_clp` — what the nota de venta form shows while the price is typed. The same preview the Resumen del negocio serves for a car with no live nota de venta (`GET /vehicles/{id}/deal-settlement`, `state: preview`), at the typed price instead of the published one: no add-ons, no seller commissions, and the one margin definition. The body is the deal-margin DTO of `GET /margin/deals/{id}` without `sale_note_id`, `display_id` and `salesperson_id` — no document exists yet.\n\n**A consigned unit is previewed on its contract’s economics**: the split is projected from the active contract’s comisión exactly as the liquidación will compute it (`settlement` carries the projection), so the dealership’s revenue is the comisión, never the whole price. A contract with no comisión on file has no split to project: its revenue is `null` with a `liquidacion` gap.\n\n`net_clp` is NET of IVA (cifras del negocio, ADR 0114 §2.2). The price a buyer agrees to on an afecto sale includes IVA, like the published price: send `round(price / 1,19)`. Writes nothing. Requires `dealership_economics:read`; 404 for a unit outside the workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Margin"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The unit being sold.",
                    "example": "4b1c2d3e-5f60-4a7b-8c9d-0e1f2a3b4c5d"
                  },
                  "net_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999,
                    "description": "The sale price the nota de venta would record, NET of IVA, in whole CLP pesos. On an afecto sale that is the price the buyer pays divided by 1,19 (rounded); on an exento sale it is the price.",
                    "example": 10084034
                  },
                  "seller_of_record": {
                    "type": "string",
                    "enum": [
                      "consignante",
                      "retoma",
                      "automotora"
                    ],
                    "description": "The leg the sale would be issued under. Omitted = the leg the unit’s tenencia suggests (`consignante` for a consigned unit, `automotora` otherwise)."
                  }
                },
                "required": [
                  "vehicle_id",
                  "net_clp"
                ],
                "additionalProperties": false
              },
              "example": {
                "vehicle_id": "e5e5e5e5-0000-4000-8000-000000000002",
                "net_clp": 10000000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The previewed deal margin",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicle_id": "e5e5e5e5-0000-4000-8000-000000000002",
                    "issued_at": "2026-09-29T15:00:00.000Z",
                    "period": "2026-09",
                    "regime": "consignacion",
                    "ingresos_clp": 800000,
                    "revenue_terms": {
                      "precio_venta_clp": 10000000,
                      "add_on_charges_clp": 0,
                      "pass_through_clp": 0,
                      "pass_through_paid_out_clp": 0,
                      "dealer_take_clp": 800000
                    },
                    "costo_venta_clp": 0,
                    "cost_terms": {
                      "payout_clp": 0,
                      "retenciones_clp": 0
                    },
                    "margen_bruto_clp": 800000,
                    "deductions": {
                      "comisiones_vendedores_clp": 0,
                      "gastos_adicionales_clp": 120000,
                      "gastos_unidad_clp": 120000,
                      "ingresos_unidad_clp": 0,
                      "desembolsos_add_on_clp": 0,
                      "gastos_fijos_clp": null
                    },
                    "margen_neto_clp": 680000,
                    "settlement": {
                      "settlement_mode": "stated_commission",
                      "amount_venta_clp": 10000000,
                      "monto_owner_clp": 9200000,
                      "deducciones_clp": 0
                    },
                    "cost_rollup": {
                      "acquisition_net_clp": 0,
                      "expense_total_clp": 120000,
                      "income_total_clp": 0,
                      "total_cost_clp": 120000,
                      "dealership_cost_clp": 120000,
                      "by_party": {
                        "automotora": {
                          "acquisition_clp": 0,
                          "expense_clp": 120000,
                          "purchase_retention_clp": 0,
                          "income_clp": 0,
                          "total_clp": 120000
                        },
                        "dueno": {
                          "acquisition_clp": 0,
                          "expense_clp": 0,
                          "purchase_retention_clp": 0,
                          "income_clp": 0,
                          "total_clp": 0
                        },
                        "cliente": {
                          "acquisition_clp": 0,
                          "expense_clp": 0,
                          "purchase_retention_clp": 0,
                          "income_clp": 0,
                          "total_clp": 0
                        }
                      },
                      "entry_count": 2,
                      "complete": true,
                      "gaps": []
                    },
                    "complete": true,
                    "gaps": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/margin/export": {
      "get": {
        "summary": "Export the deals and their margins (xlsx / csv)",
        "description": "One row per live nota de venta in the window, with every settlement column: folio, date, status, patente, vehicle, regime, seller, buyer, buyer origin channel, leads, days in stock; then the cifras del negocio, NET of IVA (sale price, Adicionales con margen, revenue, purchase cost, Margen bruto, additional costs, commissions, Utilidad del negocio), the Cobros por cuenta del comprador (cobrado / rendido, neutral); then the cifras del comprador, GROSS and labelled so (sale price con IVA, IVA, published price, discount amount and %); and `Completo` / `Pendiente` (what an incomplete sale lacks: «Sin costo», «Sin comisión», «Sin liquidación»). Every sale is a row. A figure that is not recorded or not computable is an EMPTY cell, never 0.\n\nPass `period` (`YYYY-MM`, the Margen screen’s month) OR `from`/`to` (`YYYY-MM-DD`, inclusive, on the dealership’s calendar) — one of the two is required (400 otherwise), never both. `format` is `xlsx` (default) or `csv`. **413** over 2000 deals. Requires `dealership_economics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Margin"
        ],
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/OverheadPeriod"
            },
            "required": false,
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ],
              "default": "xlsx",
              "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats."
            },
            "required": false,
            "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The planilla, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/deal-settlement": {
      "get": {
        "summary": "The Resumen del negocio of one unit",
        "description": "Everything about one car’s deal on one read: the published price, the discount (amount and %), the sale price, the purchase or consignment basis, the cost lines grouped by category with who absorbs each (`automotora` / `dueno` / `cliente`), the margin (the platform’s one margin definition, embedded whole with `complete` and `gaps`), the commission of each salesperson, the seller, the buyer and the buyer’s origin channel (`origin`: the sale note’s originating lead — its portal, else the channel of its conversation — then the origin a person declared on the buyer’s contact (`origin_channel`); `null` when neither is recorded, which every surface prints as «Sin registrar»), how many people asked about the unit, days in stock (entry → approval) and days to sell (first publication → sale).\n\nCifras del negocio are NET of IVA (ADR 0114 §2.2): `pricing.sale_price_clp` and every `margin` figure. Cifras del comprador are GROSS: `pricing.total_clp` (what the buyer paid, net + IVA), `pricing.list_price_clp` and `pricing.discount_clp` (measured against the published price, which includes IVA). Where both appear together an afecto sale reads «Precio de venta $5.806.723 neto · $6.910.000 con IVA» under a «Cifras sin IVA» heading.\n\nCommissions are frozen when the sale is approved and never change (ADR 0114 §2.4), while `margin.margen_bruto_clp` is always current: when an approved sale’s `commissions[].commission_basis_clp` differs from it (it does for sales approved before retenciones joined the Costo de compra, §2.5), say so — «Base fijada al aprobar la venta ($X). Las comisiones aprobadas no se recalculan.» — and never present the difference as an error. The PDF and the planilla print that line.\n\nThe car’s lifecycle facts ride along: `ingreso` (how the car entered stock — `compra`, `consignacion` or `retoma` — the day, and the backing document with its folio; `null` = «Sin costo»), `sale_note.emitted_by` and `sale_note.approved_by` (who emitted and who approved the sale, by name; `null` = not recorded), `basis.consignment.contract_id` (where the Liquidación lives) and `post_sale` (the trámites the approved sale opened: total, pending, overdue on the dealership day and the next deadline; `null` until the sale is approved).\n\nCobros por cuenta del comprador (the transfer tax, registry fees, notary — charged at exact cost) are in no margin figure; `pricing.pass_through_clp` (cobrado) and `pricing.pass_through_paid_out_clp` (rendido) carry the memo line «Cobrado por cuenta del comprador $X · rendido $Y».\n\n«Costo de compra» (`margin.costo_venta_clp`) is the nota de compra’s precio acordado: `margin.cost_terms.payout_clp` (paid to the seller) + `margin.cost_terms.retenciones_clp` (withheld and paid to third parties on the seller’s behalf), disclosed as «Precio acordado $X · incluye retenciones $Y». The retención lines stay in `costs.categories` for traceability with `in_purchase_cost: true`; they are not «Gastos de la unidad».\n\n`state` is `sold` (the newest live nota de venta is approved), `pending_approval` (issued, not yet approved) or `preview` (no live nota de venta: the settlement at the published price, with no discount, add-ons or commissions, flagged by a `preview` gap). A figure that is not recorded is `null`, never 0. Requires `dealership_economics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Margin"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Resumen del negocio",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicle_id": "e5e5e5e5-0000-4000-8000-000000000001",
                    "as_of": "2026-09-28T15:00:00.000Z",
                    "state": "sold",
                    "vehicle": {
                      "make": "Toyota",
                      "model": "RAV4",
                      "version": "2.0 XLE CVT",
                      "year": 2021,
                      "registration_number": "PQRS-12",
                      "vin": "JTMW43FV0MD000001",
                      "color": "Gris plata",
                      "odometer_value": 48300,
                      "odometer_unit": "km",
                      "tenencia": "propio",
                      "status": "vendido",
                      "price_clp": 12490000
                    },
                    "sale_note": {
                      "id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "display_id": "V-118",
                      "status": "approved",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "approved_at": "2026-09-20T10:30:00.000Z",
                      "approved_by": {
                        "user_id": "a1a1a1a1-0000-4000-8000-000000000003",
                        "name": "Paz Ortiz"
                      },
                      "seller_of_record": "automotora"
                    },
                    "ingreso": {
                      "kind": "compra",
                      "acquired_on": "2026-07-24",
                      "document": {
                        "type": "purchase_note",
                        "id": "e5e5e5e5-0000-4000-8000-000000000001",
                        "folio": "P-34"
                      }
                    },
                    "pricing": {
                      "list_price_clp": 12490000,
                      "sale_price_clp": 11900000,
                      "tax_clp": 0,
                      "total_clp": 11900000,
                      "discount_clp": 590000,
                      "discount_pct": 4.7,
                      "add_on_charges_clp": 100000,
                      "pass_through_clp": 89900,
                      "pass_through_paid_out_clp": 89900
                    },
                    "basis": {
                      "regime": "propia",
                      "acquisition_net_clp": 9800000,
                      "consignment": null,
                      "settlement": null
                    },
                    "costs": {
                      "categories": [
                        {
                          "category_id": "9a9a9a9a-0000-4000-8000-000000000001",
                          "name": "Mecánica",
                          "kind": "expense",
                          "total_clp": 420000,
                          "by_party": {
                            "automotora": 420000,
                            "dueno": 0,
                            "cliente": 0
                          },
                          "line_count": 2,
                          "in_purchase_cost": false
                        },
                        {
                          "category_id": "9a9a9a9a-0000-4000-8000-000000000002",
                          "name": "Detailing",
                          "kind": "expense",
                          "total_clp": 315000,
                          "by_party": {
                            "automotora": 220000,
                            "dueno": 0,
                            "cliente": 95000
                          },
                          "line_count": 2,
                          "in_purchase_cost": false
                        },
                        {
                          "category_id": "9a9a9a9a-0000-4000-8000-000000000003",
                          "name": "Comisión de crédito",
                          "kind": "income",
                          "total_clp": 150000,
                          "by_party": {
                            "automotora": 150000,
                            "dueno": 0,
                            "cliente": 0
                          },
                          "line_count": 1,
                          "in_purchase_cost": false
                        }
                      ],
                      "by_party": {
                        "automotora": {
                          "acquisition_clp": 9800000,
                          "expense_clp": 640000,
                          "purchase_retention_clp": 0,
                          "income_clp": 150000,
                          "total_clp": 10290000
                        },
                        "dueno": {
                          "acquisition_clp": 0,
                          "expense_clp": 0,
                          "purchase_retention_clp": 0,
                          "income_clp": 0,
                          "total_clp": 0
                        },
                        "cliente": {
                          "acquisition_clp": 0,
                          "expense_clp": 95000,
                          "purchase_retention_clp": 0,
                          "income_clp": 0,
                          "total_clp": 95000
                        }
                      },
                      "complete": true
                    },
                    "margin": {
                      "vehicle_id": "e5e5e5e5-0000-4000-8000-000000000001",
                      "sale_note_id": "d4d4d4d4-0000-4000-8000-000000000001",
                      "display_id": "V-118",
                      "salesperson_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "issued_at": "2026-09-19T13:00:00.000Z",
                      "period": "2026-09",
                      "regime": "propia",
                      "ingresos_clp": 12000000,
                      "revenue_terms": {
                        "precio_venta_clp": 11900000,
                        "add_on_charges_clp": 100000,
                        "pass_through_clp": 89900,
                        "pass_through_paid_out_clp": 89900,
                        "dealer_take_clp": null
                      },
                      "costo_venta_clp": 9800000,
                      "cost_terms": {
                        "payout_clp": 9800000,
                        "retenciones_clp": 0
                      },
                      "margen_bruto_clp": 2200000,
                      "deductions": {
                        "comisiones_vendedores_clp": 220000,
                        "gastos_adicionales_clp": 490000,
                        "gastos_unidad_clp": 640000,
                        "ingresos_unidad_clp": 150000,
                        "desembolsos_add_on_clp": 0,
                        "gastos_fijos_clp": null
                      },
                      "margen_neto_clp": 1490000,
                      "settlement": null,
                      "cost_rollup": {
                        "acquisition_net_clp": 9800000,
                        "expense_total_clp": 735000,
                        "income_total_clp": 150000,
                        "total_cost_clp": 10385000,
                        "dealership_cost_clp": 10290000,
                        "by_party": {
                          "automotora": {
                            "acquisition_clp": 9800000,
                            "expense_clp": 640000,
                            "purchase_retention_clp": 0,
                            "income_clp": 150000,
                            "total_clp": 10290000
                          },
                          "dueno": {
                            "acquisition_clp": 0,
                            "expense_clp": 0,
                            "purchase_retention_clp": 0,
                            "income_clp": 0,
                            "total_clp": 0
                          },
                          "cliente": {
                            "acquisition_clp": 0,
                            "expense_clp": 95000,
                            "purchase_retention_clp": 0,
                            "income_clp": 0,
                            "total_clp": 95000
                          }
                        },
                        "entry_count": 5,
                        "complete": true,
                        "gaps": []
                      },
                      "complete": true,
                      "gaps": []
                    },
                    "commissions": [
                      {
                        "participation_id": "5d1c2b3a-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                        "user_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "name": "Carla Rojas",
                        "role": "closer",
                        "commission_rate_bp": 800,
                        "commission_basis_clp": 2200000,
                        "commission_clp": 176000
                      },
                      {
                        "participation_id": "6e2d3c4b-5f60-4b7c-9d8e-0f1a2b3c4d5e",
                        "user_id": "a1a1a1a1-0000-4000-8000-000000000002",
                        "name": "Diego Muñoz",
                        "role": "collaborator",
                        "commission_rate_bp": 200,
                        "commission_basis_clp": 2200000,
                        "commission_clp": 44000
                      }
                    ],
                    "salesperson": {
                      "user_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Carla Rojas"
                    },
                    "buyer": {
                      "contact_id": "c3c3c3c3-0000-4000-8000-000000000001",
                      "name": "Ana Pérez Soto"
                    },
                    "origin": {
                      "source": "mercadolibre",
                      "channel": "whatsapp",
                      "label": "Mercado Libre",
                      "via": "lead",
                      "lead_id": "b2b2b2b2-0000-4000-8000-000000000001",
                      "conversation_id": "f6f6f6f6-0000-4000-8000-000000000001"
                    },
                    "interest": {
                      "distinct_contacts": 14,
                      "named_leads": 16,
                      "anonymous_interest": 5,
                      "first_contact_at": "2026-08-02T14:10:00.000Z",
                      "last_contact_at": "2026-09-18T21:45:00.000Z"
                    },
                    "post_sale": {
                      "total": 3,
                      "pending": 1,
                      "overdue": 0,
                      "next_due_on": "2026-10-20",
                      "completed_at": null
                    },
                    "timeline": {
                      "entered_stock_at": "2026-07-24T12:00:00.000Z",
                      "first_published_at": "2026-07-26T15:00:00.000Z",
                      "sold_at": "2026-09-19T13:00:00.000Z",
                      "approved_at": "2026-09-20T10:30:00.000Z",
                      "days_in_stock": 58,
                      "days_to_sell": 55
                    },
                    "complete": true,
                    "gaps": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/documents": {
      "get": {
        "summary": "Every document of one car",
        "description": "The car’s papers in one read (ADR 0114 §7), in the dealership’s order: how it entered (`purchase_note`, nota de compra — or the `sale_note` with `role: parte_de_pago` that took it as a trade-in), the `consignment_contract` (contrato de consignación, the mandato), each `sale_note` (nota de venta, voided ones included) followed by its `compraventa` (contrato de compraventa), the `deal_settlement` (Resumen del negocio; `preview` while the car is in stock) and the `liquidacion` (liquidación al consignante).\n\nA paper the car is expected to have and does not is listed with `status: missing` (a nota de venta before the sale, a compraventa before approval, a liquidación before the consignor is paid). Other statuses: `issued`, `approved`, `voided` (notas), `printed` / `unprinted` (compraventa, mandato — frozen instruments), `preview` / `final` (Resumen del negocio), `paid` / `withdrawal` (liquidación).\n\n`pdf` is where the PDF is read (`method` + `path` under `/api/v1`); `print` is the call that issues an unprinted instrument (needs the write scope). `signable` says whether a signed copy belongs on the document; `signed_copy` is that copy in the expediente (upload it with `POST /vehicle-attachments` and `signed_document_type` + `signed_document_id`), `null` when missing and ABSENT for a caller without `vehicle_registry:read`.\n\nRequires `marketplace:read`. Each document type is listed only for a caller holding its read scope (`purchase_notes:read`, `consignments:read`, `sale_notes:read`, `dealership_economics:read`). No amounts.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The car’s documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "documents": [
                      {
                        "type": "purchase_note",
                        "id": "d5d5d5d5-0000-4000-8000-000000000001",
                        "folio": "P-34",
                        "issued_at": "2026-08-02T14:10:00.000Z",
                        "printed_at": null,
                        "status": "issued",
                        "pdf": {
                          "method": "GET",
                          "path": "/purchase-notes/d5d5d5d5-0000-4000-8000-000000000001/pdf"
                        },
                        "print": null,
                        "signable": true,
                        "signed_copy": null
                      },
                      {
                        "type": "sale_note",
                        "id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "folio": "V-12",
                        "issued_at": "2026-09-20T15:02:00.000Z",
                        "printed_at": null,
                        "status": "approved",
                        "pdf": {
                          "method": "GET",
                          "path": "/sale-notes/d4d4d4d4-0000-4000-8000-000000000001/pdf"
                        },
                        "print": null,
                        "signable": true,
                        "signed_copy": null
                      },
                      {
                        "type": "compraventa",
                        "id": "d4d4d4d4-0000-4000-8000-000000000001",
                        "folio": "V-12",
                        "issued_at": "2026-09-20T16:40:00.000Z",
                        "printed_at": "2026-09-20T16:40:00.000Z",
                        "status": "printed",
                        "pdf": {
                          "method": "GET",
                          "path": "/sale-notes/d4d4d4d4-0000-4000-8000-000000000001/compraventa/pdf"
                        },
                        "print": null,
                        "signable": true,
                        "signed_copy": {
                          "attachment_id": "f1f1f1f1-0000-4000-8000-000000000003",
                          "filename": "compraventa-firmada.pdf",
                          "mime_type": "application/pdf",
                          "uploaded_at": "2026-09-21T10:05:00.000Z"
                        }
                      },
                      {
                        "type": "deal_settlement",
                        "id": null,
                        "folio": null,
                        "issued_at": "2026-09-20T15:30:00.000Z",
                        "printed_at": null,
                        "status": "final",
                        "pdf": {
                          "method": "GET",
                          "path": "/vehicles/e1e1e1e1-0000-4000-8000-000000000001/deal-settlement/pdf"
                        },
                        "print": null,
                        "signable": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/deal-settlement/pdf": {
      "get": {
        "summary": "Print the Resumen del negocio (PDF)",
        "description": "The Resumen del negocio as a PDF (`application/pdf`, served `inline`): the workspace’s default `deal_settlement` template — or `template_id` — filled with the deal’s facts, followed by a fixed table of the result walk headed «Cifras sin IVA», the costs by category and who absorbs them, and the commissions. Every figure of the walk is net of IVA (ADR 0114 §2.2); an afecto sale’s price reads «$5.806.723 neto · $6.910.000 con IVA», and no gross figure is a step of the walk. With no live template it prints the standard text. A missing fact prints «no registrado»; nothing is refused. A preview carries a «VISTA PREVIA» banner. `X-Template-Version` names the prose used. Requires `dealership_economics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Margin"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "template_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The printable Resumen del negocio, named `resumen-del-negocio-<folio>.pdf`",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/deal-settlement/export": {
      "get": {
        "summary": "Export the Resumen del negocio (xlsx / csv)",
        "description": "The same Resumen del negocio as a planilla. `xlsx` (default) carries three sheets — **Resumen** (the result walk, net of IVA — its `Monto sin IVA` column sums to the Utilidad; an afecto sale’s gross price, the published price and the discount are stated as facts, never in that column — and the deal’s facts), **Costos** (by category and party) and **Comisiones** (per salesperson, with a note under the table when an approved sale’s frozen commission base differs from the current Margen bruto); `csv` carries the Resumen. Requires `dealership_economics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Margin"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ],
              "default": "xlsx",
              "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats."
            },
            "required": false,
            "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The planilla, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sale-notes/export": {
      "get": {
        "summary": "Export the notas de venta (xlsx / csv)",
        "description": "The Ventas list as a planilla: one row per nota de venta issued in the window — folio, issue and approval dates, status, patente, vehicle, regime, seller, buyer, the buyer’s origin channel, the published price, the discount (amount and %), the sale price, IVA, tax treatment and the void reason. No cost figure (that is `GET /margin/export`). `from`/`to` are inclusive `YYYY-MM-DD` days on the dealership’s calendar, `status` narrows to `issued` | `approved` | `voided`; all optional. `format` is `xlsx` (default) or `csv`. **413** over 2000 documents. Requires `sale_notes:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sale Notes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "issued",
                "approved",
                "voided"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ],
              "default": "xlsx",
              "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats."
            },
            "required": false,
            "description": "`xlsx` (a workbook, typed cells) or `csv` (`;`-separated, UTF-8 with BOM, dd-mm-aaaa dates, decimal comma; the first sheet only). Date-times are on the America/Santiago clock in both formats.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The planilla, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/seller-performance/export": {
      "get": {
        "summary": "Exportar comisiones — the month-end payroll file (Excel or CSV)",
        "description": "The same figures as the report above, rendered as an .xlsx workbook (default) or, with `?format=csv`, a `;`-separated UTF-8 CSV of the ledger, on the same export rail as `GET /vehicles/export`: sheet \"Comisiones\" is one row per seller per deal with the commission arithmetic visible (base × «Tasa %» → comisión; rates are percentages, never basis points; dates are dd-mm-aaaa hh:mm on the America/Santiago clock), sheet \"Resumen\" is what to pay each person. Same `?window=` parameter and the same `dealership_economics:read` gate as the report — a file must not be pullable for a period or by a principal the screen refuses. `Content-Disposition` carries the generated filename (`comisiones-<periodo>-<YYYY-MM-DD>.xlsx`). Audited.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Insights"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which window. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted. `calendar_month` is the one payroll wants: commission is paid monthly, and the month is the DEALERSHIP’s (America/Santiago), the same boundary the commission itself was resolved under."
            },
            "required": false,
            "description": "Which window. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted. `calendar_month` is the one payroll wants: commission is paid monthly, and the month is the DEALERSHIP’s (America/Santiago), the same boundary the commission itself was resolved under.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, INCLUSIVE."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, INCLUSIVE.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, EXCLUSIVE."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, EXCLUSIVE.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ],
              "description": "`xlsx` (default; sheets \"Comisiones\" + \"Resumen\") or `csv` (the ledger: `;`-separated, UTF-8 with BOM, dd-mm-aaaa dates)."
            },
            "required": false,
            "description": "`xlsx` (default; sheets \"Comisiones\" + \"Resumen\") or `csv` (the ledger: `;`-separated, UTF-8 with BOM, dd-mm-aaaa dates).",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The comisiones workbook (xlsx)",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"comisiones-….xlsx\"",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs": {
      "get": {
        "summary": "List the graphs in the workspace",
        "description": "Graph rows only — no nodes. Fetch one graph to get its nodes. `meta.total` is the length of the list, not a page count: this endpoint is unpaginated.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "Every graph in the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas + postventa",
                      "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                      "topology": "supervisor",
                      "status": "active",
                      "is_default": false,
                      "config": {
                        "default_node_key": "triage"
                      },
                      "published_version_id": "b4b4b4b4-2000-4000-8000-000000000000",
                      "draft": null,
                      "created_at": "2026-09-05T12:00:00.000Z",
                      "updated_at": "2026-09-18T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a graph",
        "description": "Creates the graph and, if `nodes` is supplied, its nodes in one call. Each node references exactly one of `ai_agent_id` / `subgraph_id` — supplying both, or neither, is a 400. The response is `{ graph, nodes }` — not a bare graph — because the nodes come back with the ids the caller needs to address them. `is_default: true` makes this the graph conversations run against and clears the flag on whichever graph held it. Created graphs are unpublished: they have no version and nothing runs them until POST /ai-agent-graphs/{id}/publish. The `parallel` and `evaluator_loop` topologies are gated to internal tenants: publishing one without `settings.topology_flags.<topology>` answers 409. Creating and drafting them is not gated — only publish is, so a graph can be built long before it is allowed to go live.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "topology": {
                    "type": "string",
                    "enum": [
                      "single",
                      "sequential",
                      "routing",
                      "supervisor",
                      "parallel",
                      "evaluator_loop"
                    ]
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "merge_strategy": {
                        "type": "string",
                        "enum": [
                          "first_response",
                          "voting",
                          "concat"
                        ]
                      },
                      "voter_node_key": {
                        "type": "string"
                      },
                      "max_iterations": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 3
                      },
                      "default_node_key": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": false
                  },
                  "nodes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ai_agent_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "subgraph_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "root",
                            "leaf",
                            "supervisor",
                            "classifier",
                            "chain_step",
                            "parallel_branch",
                            "evaluator",
                            "optimizer"
                          ]
                        },
                        "node_key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 80
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 99
                        },
                        "config": {
                          "type": "object",
                          "properties": {
                            "disabled_tool_keys": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "handoff_rules": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "when": {
                                    "type": "object",
                                    "additionalProperties": {
                                      "anyOf": [
                                        {
                                          "type": "string"
                                        },
                                        {
                                          "type": "number"
                                        },
                                        {
                                          "type": "boolean"
                                        },
                                        {
                                          "type": "object",
                                          "properties": {
                                            "eq": {
                                              "anyOf": [
                                                {
                                                  "type": "string"
                                                },
                                                {
                                                  "type": "number"
                                                },
                                                {
                                                  "type": "boolean"
                                                }
                                              ]
                                            },
                                            "ne": {
                                              "anyOf": [
                                                {
                                                  "type": "string"
                                                },
                                                {
                                                  "type": "number"
                                                },
                                                {
                                                  "type": "boolean"
                                                }
                                              ]
                                            },
                                            "gt": {
                                              "type": "number"
                                            },
                                            "gte": {
                                              "type": "number"
                                            },
                                            "lt": {
                                              "type": "number"
                                            },
                                            "lte": {
                                              "type": "number"
                                            },
                                            "in": {
                                              "type": "array",
                                              "items": {
                                                "anyOf": [
                                                  {
                                                    "type": "string"
                                                  },
                                                  {
                                                    "type": "number"
                                                  },
                                                  {
                                                    "type": "boolean"
                                                  }
                                                ]
                                              }
                                            }
                                          },
                                          "additionalProperties": false
                                        }
                                      ]
                                    }
                                  },
                                  "then": {
                                    "type": "string",
                                    "minLength": 1
                                  }
                                },
                                "required": [
                                  "when",
                                  "then"
                                ]
                              }
                            },
                            "branch_label": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "instructions": {
                              "type": "string",
                              "maxLength": 8000
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "required": [
                        "kind",
                        "node_key"
                      ]
                    }
                  },
                  "is_default": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "topology"
                ]
              },
              "example": {
                "name": "Ventas + postventa",
                "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                "topology": "supervisor",
                "nodes": [
                  {
                    "kind": "supervisor",
                    "node_key": "triage",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001"
                  },
                  {
                    "kind": "leaf",
                    "node_key": "postventa",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000002"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The graph and its nodes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "graph": {
                      "id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas + postventa",
                      "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                      "topology": "supervisor",
                      "status": "active",
                      "is_default": false,
                      "config": {
                        "default_node_key": "triage"
                      },
                      "published_version_id": "b4b4b4b4-2000-4000-8000-000000000000",
                      "draft": null,
                      "created_at": "2026-09-05T12:00:00.000Z",
                      "updated_at": "2026-09-18T09:00:00.000Z"
                    },
                    "nodes": [
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000001",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": null,
                        "subgraph_id": null,
                        "kind": "supervisor",
                        "node_key": "triage",
                        "position": 0,
                        "config": {
                          "branch_label": "Triage"
                        }
                      },
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000002",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "subgraph_id": null,
                        "kind": "leaf",
                        "node_key": "ventas",
                        "position": 1,
                        "config": {}
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ai-agent-graphs/{id}": {
      "get": {
        "summary": "Fetch one graph with its nodes",
        "description": "The graph row with a `nodes` array spliced in. These are the LIVE nodes; if the graph carries a draft, what will run after the next publish is in `draft`, which this returns untouched.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The graph and its nodes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b4b4b4b4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas + postventa",
                    "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                    "topology": "supervisor",
                    "status": "active",
                    "is_default": false,
                    "config": {
                      "default_node_key": "triage"
                    },
                    "published_version_id": "b4b4b4b4-2000-4000-8000-000000000000",
                    "draft": null,
                    "created_at": "2026-09-05T12:00:00.000Z",
                    "updated_at": "2026-09-18T09:00:00.000Z",
                    "nodes": [
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000001",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": null,
                        "subgraph_id": null,
                        "kind": "supervisor",
                        "node_key": "triage",
                        "position": 0,
                        "config": {
                          "branch_label": "Triage"
                        }
                      },
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000002",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "subgraph_id": null,
                        "kind": "leaf",
                        "node_key": "ventas",
                        "position": 1,
                        "config": {}
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a graph",
        "description": "Deletes the graph and its nodes. The `ai_agent` rows the nodes pointed at are untouched — a graph owns its wiring, never its agents.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a graph",
        "description": "Metadata and shape only — nodes have their own endpoints. At least one field is required. Changing `topology` re-validates the graph against its existing nodes and fails 400 if they no longer satisfy it, so reshaping usually means editing the nodes in the same session. `is_default: true` clears the flag on the previous default.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "topology": {
                    "type": "string",
                    "enum": [
                      "single",
                      "sequential",
                      "routing",
                      "supervisor",
                      "parallel",
                      "evaluator_loop"
                    ]
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "merge_strategy": {
                        "type": "string",
                        "enum": [
                          "first_response",
                          "voting",
                          "concat"
                        ]
                      },
                      "voter_node_key": {
                        "type": "string"
                      },
                      "max_iterations": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 3
                      },
                      "default_node_key": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": false
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "testing",
                      "inactive"
                    ]
                  },
                  "is_default": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "name": "Ventas + postventa (CL)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated graph",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b4b4b4b4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas + postventa (CL)",
                    "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                    "topology": "supervisor",
                    "status": "active",
                    "is_default": false,
                    "config": {
                      "default_node_key": "triage"
                    },
                    "published_version_id": "b4b4b4b4-2000-4000-8000-000000000000",
                    "draft": null,
                    "created_at": "2026-09-05T12:00:00.000Z",
                    "updated_at": "2026-09-18T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/nodes": {
      "post": {
        "summary": "Add a node to a graph",
        "description": "Each node references exactly one of `ai_agent_id` / `subgraph_id` — supplying both, or neither, is a 400. The whole graph is re-validated with the new node included, so a node that breaks the topology is refused with 400 and nothing is written. Node writes hit the LIVE graph directly — they do not go through the draft.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ai_agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "subgraph_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "root",
                      "leaf",
                      "supervisor",
                      "classifier",
                      "chain_step",
                      "parallel_branch",
                      "evaluator",
                      "optimizer"
                    ]
                  },
                  "node_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 99
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "disabled_tool_keys": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "handoff_rules": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "when": {
                              "type": "object",
                              "additionalProperties": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "number"
                                  },
                                  {
                                    "type": "boolean"
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "eq": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "number"
                                          },
                                          {
                                            "type": "boolean"
                                          }
                                        ]
                                      },
                                      "ne": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "number"
                                          },
                                          {
                                            "type": "boolean"
                                          }
                                        ]
                                      },
                                      "gt": {
                                        "type": "number"
                                      },
                                      "gte": {
                                        "type": "number"
                                      },
                                      "lt": {
                                        "type": "number"
                                      },
                                      "lte": {
                                        "type": "number"
                                      },
                                      "in": {
                                        "type": "array",
                                        "items": {
                                          "anyOf": [
                                            {
                                              "type": "string"
                                            },
                                            {
                                              "type": "number"
                                            },
                                            {
                                              "type": "boolean"
                                            }
                                          ]
                                        }
                                      }
                                    },
                                    "additionalProperties": false
                                  }
                                ]
                              }
                            },
                            "then": {
                              "type": "string",
                              "minLength": 1
                            }
                          },
                          "required": [
                            "when",
                            "then"
                          ]
                        }
                      },
                      "branch_label": {
                        "type": "string",
                        "maxLength": 80
                      },
                      "instructions": {
                        "type": "string",
                        "maxLength": 8000
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "kind",
                  "node_key"
                ]
              },
              "example": {
                "kind": "leaf",
                "node_key": "finanzas",
                "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000002"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created node",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b4b4b4b4-1000-4000-8000-000000000002",
                    "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000002",
                    "subgraph_id": null,
                    "kind": "leaf",
                    "node_key": "finanzas",
                    "position": 1,
                    "config": {}
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/nodes/{nodeId}": {
      "delete": {
        "summary": "Delete one node",
        "description": "Unlike create and update, deletion is NOT re-validated: the graph is allowed to be left in a shape that could not be published. That is deliberate — removing a node is usually the first step of a rewire — but it means the next publish is where the breakage surfaces.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "nodeId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update one node",
        "description": "At least one field is required. As with create, the graph is re-validated with the patch applied before anything is written.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "nodeId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ai_agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "subgraph_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "root",
                      "leaf",
                      "supervisor",
                      "classifier",
                      "chain_step",
                      "parallel_branch",
                      "evaluator",
                      "optimizer"
                    ]
                  },
                  "node_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 99
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "disabled_tool_keys": {
                        "type": "array",
                        "items": {
                          "type": "string"
                        }
                      },
                      "handoff_rules": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "when": {
                              "type": "object",
                              "additionalProperties": {
                                "anyOf": [
                                  {
                                    "type": "string"
                                  },
                                  {
                                    "type": "number"
                                  },
                                  {
                                    "type": "boolean"
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "eq": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "number"
                                          },
                                          {
                                            "type": "boolean"
                                          }
                                        ]
                                      },
                                      "ne": {
                                        "anyOf": [
                                          {
                                            "type": "string"
                                          },
                                          {
                                            "type": "number"
                                          },
                                          {
                                            "type": "boolean"
                                          }
                                        ]
                                      },
                                      "gt": {
                                        "type": "number"
                                      },
                                      "gte": {
                                        "type": "number"
                                      },
                                      "lt": {
                                        "type": "number"
                                      },
                                      "lte": {
                                        "type": "number"
                                      },
                                      "in": {
                                        "type": "array",
                                        "items": {
                                          "anyOf": [
                                            {
                                              "type": "string"
                                            },
                                            {
                                              "type": "number"
                                            },
                                            {
                                              "type": "boolean"
                                            }
                                          ]
                                        }
                                      }
                                    },
                                    "additionalProperties": false
                                  }
                                ]
                              }
                            },
                            "then": {
                              "type": "string",
                              "minLength": 1
                            }
                          },
                          "required": [
                            "when",
                            "then"
                          ]
                        }
                      },
                      "branch_label": {
                        "type": "string",
                        "maxLength": 80
                      },
                      "instructions": {
                        "type": "string",
                        "maxLength": 8000
                      }
                    },
                    "additionalProperties": false
                  }
                }
              },
              "example": {
                "position": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated node",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b4b4b4b4-1000-4000-8000-000000000002",
                    "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "subgraph_id": null,
                    "kind": "leaf",
                    "node_key": "ventas",
                    "position": 2,
                    "config": {}
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/draft": {
      "get": {
        "summary": "Fetch the pending draft",
        "description": "Answers 200 with `data: null` when there is no draft — absence of a draft is not a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft, or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "topology": "supervisor",
                    "config": {
                      "default_node_key": "triage"
                    },
                    "nodes": [
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000001",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": null,
                        "subgraph_id": null,
                        "kind": "supervisor",
                        "node_key": "triage",
                        "position": 0,
                        "config": {
                          "branch_label": "Triage"
                        }
                      },
                      {
                        "id": "b4b4b4b4-1000-4000-8000-000000000002",
                        "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                        "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "subgraph_id": null,
                        "kind": "leaf",
                        "node_key": "ventas",
                        "position": 1,
                        "config": {}
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Save the draft",
        "description": "Replaces the whole draft — this is a PUT, so omitting a key drops it rather than leaving the previous value. The draft is intentionally NOT validated: a half-edited graph is a legal thing to save, and validation is deferred to publish. Returns the graph, with the draft on it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "topology": {
                    "type": "string",
                    "enum": [
                      "single",
                      "sequential",
                      "routing",
                      "supervisor",
                      "parallel",
                      "evaluator_loop"
                    ]
                  },
                  "config": {
                    "type": "object",
                    "properties": {
                      "merge_strategy": {
                        "type": "string",
                        "enum": [
                          "first_response",
                          "voting",
                          "concat"
                        ]
                      },
                      "voter_node_key": {
                        "type": "string"
                      },
                      "max_iterations": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 3
                      },
                      "default_node_key": {
                        "type": "string"
                      }
                    },
                    "additionalProperties": false
                  },
                  "nodes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "ai_agent_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "subgraph_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "root",
                            "leaf",
                            "supervisor",
                            "classifier",
                            "chain_step",
                            "parallel_branch",
                            "evaluator",
                            "optimizer"
                          ]
                        },
                        "node_key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 80
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 99
                        },
                        "config": {
                          "type": "object",
                          "properties": {
                            "disabled_tool_keys": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "handoff_rules": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "when": {
                                    "type": "object",
                                    "additionalProperties": {
                                      "anyOf": [
                                        {
                                          "type": "string"
                                        },
                                        {
                                          "type": "number"
                                        },
                                        {
                                          "type": "boolean"
                                        },
                                        {
                                          "type": "object",
                                          "properties": {
                                            "eq": {
                                              "anyOf": [
                                                {
                                                  "type": "string"
                                                },
                                                {
                                                  "type": "number"
                                                },
                                                {
                                                  "type": "boolean"
                                                }
                                              ]
                                            },
                                            "ne": {
                                              "anyOf": [
                                                {
                                                  "type": "string"
                                                },
                                                {
                                                  "type": "number"
                                                },
                                                {
                                                  "type": "boolean"
                                                }
                                              ]
                                            },
                                            "gt": {
                                              "type": "number"
                                            },
                                            "gte": {
                                              "type": "number"
                                            },
                                            "lt": {
                                              "type": "number"
                                            },
                                            "lte": {
                                              "type": "number"
                                            },
                                            "in": {
                                              "type": "array",
                                              "items": {
                                                "anyOf": [
                                                  {
                                                    "type": "string"
                                                  },
                                                  {
                                                    "type": "number"
                                                  },
                                                  {
                                                    "type": "boolean"
                                                  }
                                                ]
                                              }
                                            }
                                          },
                                          "additionalProperties": false
                                        }
                                      ]
                                    }
                                  },
                                  "then": {
                                    "type": "string",
                                    "minLength": 1
                                  }
                                },
                                "required": [
                                  "when",
                                  "then"
                                ]
                              }
                            },
                            "branch_label": {
                              "type": "string",
                              "maxLength": 80
                            },
                            "instructions": {
                              "type": "string",
                              "maxLength": 8000
                            }
                          },
                          "additionalProperties": false
                        }
                      },
                      "required": [
                        "kind",
                        "node_key"
                      ]
                    }
                  }
                }
              },
              "example": {
                "topology": "supervisor",
                "nodes": [
                  {
                    "kind": "supervisor",
                    "node_key": "triage",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001"
                  },
                  {
                    "kind": "leaf",
                    "node_key": "ventas",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001"
                  },
                  {
                    "kind": "leaf",
                    "node_key": "postventa",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000002"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The graph carrying the saved draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b4b4b4b4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas + postventa",
                    "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                    "topology": "supervisor",
                    "status": "active",
                    "is_default": false,
                    "config": {
                      "default_node_key": "triage"
                    },
                    "published_version_id": "b4b4b4b4-2000-4000-8000-000000000000",
                    "draft": {
                      "topology": "supervisor",
                      "nodes": [
                        {
                          "kind": "supervisor",
                          "node_key": "triage",
                          "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001"
                        },
                        {
                          "kind": "leaf",
                          "node_key": "ventas",
                          "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001"
                        },
                        {
                          "kind": "leaf",
                          "node_key": "postventa",
                          "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000002"
                        }
                      ]
                    },
                    "created_at": "2026-09-05T12:00:00.000Z",
                    "updated_at": "2026-09-18T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard the draft",
        "description": "Drops the pending edit; the live graph is untouched. Discarding when there is no draft is a no-op, not a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/publish": {
      "post": {
        "summary": "Publish the graph",
        "description": "The only validating write on this resource, and the one with real consequences. In order: enforce the tenant `editable_window` (409 outside it); promote the draft onto the live shape, **replacing the node set wholesale** if the draft carries `nodes`; hard-validate (400 on a bad graph — and the draft has already been promoted by then); snapshot into a new `ai_agent_graph_version`; move `published_version_id`; dispatch the `ai_agent_graph.publish` webhook; The `parallel` and `evaluator_loop` topologies are gated to internal tenants: publishing one without `settings.topology_flags.<topology>` answers 409. Creating and drafting them is not gated — only publish is, so a graph can be built long before it is allowed to go live. Publishing never runs the graph eval suite (evals run only on demand: `POST /ai-agent-graphs/{id}/tests/run`); `skip_eval` is accepted and ignored. Returns `{ graph, version }`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "skip_eval": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "label": "Agrega derivación a postventa"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The published graph and its new version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "graph": {
                      "id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas + postventa",
                      "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                      "topology": "supervisor",
                      "status": "active",
                      "is_default": false,
                      "config": {
                        "default_node_key": "triage"
                      },
                      "published_version_id": "b4b4b4b4-2000-4000-8000-000000000001",
                      "draft": null,
                      "created_at": "2026-09-05T12:00:00.000Z",
                      "updated_at": "2026-09-18T09:00:00.000Z"
                    },
                    "version": {
                      "id": "b4b4b4b4-2000-4000-8000-000000000001",
                      "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "version_number": 3,
                      "label": "Agrega derivación a postventa",
                      "topology": "supervisor",
                      "created_at": "2026-09-22T09:05:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/versions": {
      "get": {
        "summary": "List the published versions",
        "description": "The snapshot timeline, newest first, capped at the 50 most recent — the limit is fixed and not a query parameter. Each row carries the full `node_snapshot` that was live at publish time, so a version is enough to reconstruct the graph without any other read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Version snapshots",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b4b4b4b4-2000-4000-8000-000000000001",
                      "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "version_number": 3,
                      "label": "Agrega derivación a postventa",
                      "topology": "supervisor",
                      "created_at": "2026-09-22T09:05:00.000Z"
                    },
                    {
                      "id": "b4b4b4b4-2000-4000-8000-000000000000",
                      "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "version_number": 2,
                      "label": null,
                      "topology": "supervisor",
                      "created_at": "2026-09-18T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agent-graphs/{id}/versions/{version}/restore": {
      "post": {
        "summary": "Restore a past version",
        "description": "Writes the snapshot back as the live shape and then re-publishes, so a restore is itself a NEW version rather than a pointer move — the timeline only ever grows. Two consequences worth planning for: the new version's label is always `Restored from v{n}` (any label you send is ignored), and because the tail of this is a real publish it inherits every publish gate — the editable window, topology flags, and validation of the snapshot against TODAY's rules. A version published under a since-revoked topology flag can therefore refuse to restore. `{version}` is the `version_number` from the versions list, not the version row's uuid.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The restored graph and its new version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "graph": {
                      "id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas + postventa",
                      "description": "Deriva entre el agente de ventas y el de postventa según la intención del cliente.",
                      "topology": "supervisor",
                      "status": "active",
                      "is_default": false,
                      "config": {
                        "default_node_key": "triage"
                      },
                      "published_version_id": "b4b4b4b4-2000-4000-8000-000000000002",
                      "draft": null,
                      "created_at": "2026-09-05T12:00:00.000Z",
                      "updated_at": "2026-09-18T09:00:00.000Z"
                    },
                    "version": {
                      "id": "b4b4b4b4-2000-4000-8000-000000000002",
                      "graph_id": "b4b4b4b4-0000-4000-8000-000000000001",
                      "version_number": 4,
                      "label": "Restored from v2",
                      "topology": "supervisor",
                      "created_at": "2026-09-22T09:10:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/assignment-rules": {
      "get": {
        "summary": "List assignment rules",
        "description": "Every rule in the workspace, ordered by `priority` ascending — the exact order the router evaluates them in. Unpaginated: routing tables hold a handful of rules, not thousands.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "responses": {
          "200": {
            "description": "The workspace’s rules, evaluation order — responsable del auto, then a consignación intent rule, then an ordinary portal-scoped rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "5c6f2a10-9b2e-4f7a-8b8b-1a2b3c4d5e6f",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Responsable del auto",
                      "enabled": true,
                      "priority": 0,
                      "match_sources": [],
                      "match_channels": [],
                      "match_lead_intents": [],
                      "team_id": null,
                      "assignee_user_ids": null,
                      "assignee_weights": {},
                      "target_vehicle_owner": true,
                      "assignment_mode": "auto_round_robin",
                      "is_portal_default": false,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    },
                    {
                      "id": "6d7f3b21-0c3f-4a8b-9c9c-2b3c4d5e6f7a",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Consignación → equipo Consignaciones",
                      "enabled": true,
                      "priority": 1,
                      "match_sources": [],
                      "match_channels": [],
                      "match_lead_intents": [
                        "sell",
                        "trade_in"
                      ],
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "assignee_weights": {},
                      "target_vehicle_owner": false,
                      "assignment_mode": "auto_round_robin",
                      "is_portal_default": false,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    },
                    {
                      "id": "d5d5d5d5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Mercado Libre → equipo Providencia",
                      "enabled": true,
                      "priority": 2,
                      "match_sources": [
                        "mercadolibre"
                      ],
                      "match_channels": [],
                      "match_lead_intents": [],
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "assignee_weights": {},
                      "target_vehicle_owner": false,
                      "assignment_mode": "auto_round_robin",
                      "is_portal_default": false,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an assignment rule",
        "description": "Exactly one destination: `team_id`, a non-empty `assignee_user_ids`, OR `target_vehicle_owner: true` (combining it with either of the other two is a 400 — a rule has exactly one destination kind). Empty/absent `match_sources` / `match_channels` / `match_lead_intents` are wildcards (`match_lead_intents` ∈ buy | sell | financing | trade_in, the linked lead's `intent`). A client-supplied `priority` is rejected — new rules append LAST; move them with `PUT /assignment-rules/order`. `assignment_mode: \"manual\"` is a 400 (channel-only mode); `auto_round_robin_online` on a `target_vehicle_owner` rule falls through to the next rule when the named person is offline, rather than leaving the thread unassigned.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "match_sources": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "enum": [
                        "conversation",
                        "marketplace",
                        "manual",
                        "import",
                        "ai_agent",
                        "chileautos",
                        "yapo",
                        "mercadolibre",
                        "website"
                      ]
                    },
                    "maxItems": 9
                  },
                  "match_channels": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 50
                    },
                    "maxItems": 50
                  },
                  "match_lead_intents": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "enum": [
                        "buy",
                        "sell",
                        "financing",
                        "trade_in"
                      ]
                    },
                    "maxItems": 4
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "assignee_user_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "assignee_weights": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "description": "Per-member share of the rotation, {user_id: 1..100}. A member not listed weighs 1, so {} is plain round robin. Weights A=9, B=9, C=1 give C one conversation in every 19, spread out (smooth weighted round robin). Keys must be members of the pool (assignee_user_ids, or the team when one is set). Used by auto_round_robin and auto_round_robin_online; ignored by auto_least_busy.",
                    "example": {
                      "7c9e6679-7425-40de-944b-e07fc1f90ae7": 9,
                      "16fd2706-8baf-433b-82eb-8c7fada847da": 9
                    }
                  },
                  "target_vehicle_owner": {
                    "type": "boolean"
                  },
                  "assignment_mode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "auto_round_robin",
                      "auto_round_robin_online",
                      "auto_least_busy"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Mercado Libre → equipo Providencia",
                "match_sources": [
                  "mercadolibre"
                ],
                "team_id": "cccccccc-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Mercado Libre → equipo Providencia",
                    "enabled": true,
                    "priority": 0,
                    "match_sources": [
                      "mercadolibre"
                    ],
                    "match_channels": [],
                    "match_lead_intents": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "assignee_weights": {},
                    "target_vehicle_owner": false,
                    "assignment_mode": "auto_round_robin",
                    "is_portal_default": false,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/assignment-rules/portal-defaults": {
      "get": {
        "summary": "List portal defaults",
        "description": "The \"Predeterminado por marketplace\" cards (ADR 0084): one per portal source (mercadolibre / yapo / chileautos) the workspace has configured. Each is an assignment rule flagged `is_portal_default`, evaluated AFTER every custom rule.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "responses": {
          "200": {
            "description": "The workspace’s portal defaults",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "source": "mercadolibre",
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "assignment_mode": "auto_round_robin"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/assignment-rules/portal-defaults/{source}": {
      "put": {
        "summary": "Set a portal default",
        "description": "Upserts the card’s WHOLE config for one portal source. Exactly one destination (`team_id` or `assignee_user_ids`); `assignment_mode: \"manual\"` is legal here (the thread queues for manual pickup and the chain STOPS). A body with no destination and a non-manual mode CLEARS the default (204).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "mercadolibre",
                "yapo",
                "chileautos"
              ]
            },
            "required": true,
            "name": "source",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignment_mode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "auto_round_robin",
                      "auto_round_robin_online",
                      "auto_least_busy",
                      "manual"
                    ]
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "assignee_user_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "assignee_weights": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "description": "Per-member share of the rotation, {user_id: 1..100}. A member not listed weighs 1, so {} is plain round robin. Weights A=9, B=9, C=1 give C one conversation in every 19, spread out (smooth weighted round robin). Keys must be members of the pool (assignee_user_ids, or the team when one is set). Used by auto_round_robin and auto_round_robin_online; ignored by auto_least_busy.",
                    "example": {
                      "7c9e6679-7425-40de-944b-e07fc1f90ae7": 9,
                      "16fd2706-8baf-433b-82eb-8c7fada847da": 9
                    }
                  }
                }
              },
              "example": {
                "team_id": "cccccccc-0000-4000-8000-000000000001",
                "assignment_mode": "auto_round_robin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored portal default",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "source": "mercadolibre",
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "assignment_mode": "auto_round_robin"
                  }
                }
              }
            }
          },
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/assignment-rules/{id}": {
      "get": {
        "summary": "Get an assignment rule",
        "description": "The rule, including its destination and its evaluation `priority`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Mercado Libre → equipo Providencia",
                    "enabled": true,
                    "priority": 0,
                    "match_sources": [
                      "mercadolibre"
                    ],
                    "match_channels": [],
                    "match_lead_intents": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "assignee_weights": {},
                    "target_vehicle_owner": false,
                    "assignment_mode": "auto_round_robin",
                    "is_portal_default": false,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an assignment rule",
        "description": "Removes the rule. Every OTHER rule keeps its own `priority` — deleting does not compact the sequence, so the remaining order is unchanged.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update an assignment rule",
        "description": "Partial update. Setting `team_id` nulls `assignee_user_ids` (and vice versa), and setting `target_vehicle_owner: true` nulls BOTH — the destination stays exactly one thing across the three kinds. `priority` is not writable here; use `PUT /assignment-rules/order`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "match_sources": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "enum": [
                        "conversation",
                        "marketplace",
                        "manual",
                        "import",
                        "ai_agent",
                        "chileautos",
                        "yapo",
                        "mercadolibre",
                        "website"
                      ]
                    },
                    "maxItems": 9
                  },
                  "match_channels": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 50
                    },
                    "maxItems": 50
                  },
                  "match_lead_intents": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "enum": [
                        "buy",
                        "sell",
                        "financing",
                        "trade_in"
                      ]
                    },
                    "maxItems": 4
                  },
                  "team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "assignee_user_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "assignee_weights": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "integer",
                      "minimum": 1,
                      "maximum": 100
                    },
                    "description": "Per-member share of the rotation, {user_id: 1..100}. A member not listed weighs 1, so {} is plain round robin. Weights A=9, B=9, C=1 give C one conversation in every 19, spread out (smooth weighted round robin). Keys must be members of the pool (assignee_user_ids, or the team when one is set). Used by auto_round_robin and auto_round_robin_online; ignored by auto_least_busy.",
                    "example": {
                      "7c9e6679-7425-40de-944b-e07fc1f90ae7": 9,
                      "16fd2706-8baf-433b-82eb-8c7fada847da": 9
                    }
                  },
                  "target_vehicle_owner": {
                    "type": "boolean"
                  },
                  "assignment_mode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "auto_round_robin",
                      "auto_round_robin_online",
                      "auto_least_busy"
                    ]
                  }
                }
              },
              "example": {
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d5d5d5d5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Mercado Libre → equipo Providencia",
                    "enabled": false,
                    "priority": 0,
                    "match_sources": [
                      "mercadolibre"
                    ],
                    "match_channels": [],
                    "match_lead_intents": [],
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "assignee_user_ids": null,
                    "assignee_weights": {},
                    "target_vehicle_owner": false,
                    "assignment_mode": "auto_round_robin",
                    "is_portal_default": false,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/assignment-rules/order": {
      "put": {
        "summary": "Reorder the assignment rules",
        "description": "Atomic, all-or-nothing rewrite of every rule’s `priority` from the given id order. `ids` must be the COMPLETE set of the workspace’s rules, each exactly once — a partial list is a 400, never a partial apply.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Assignment rules"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "ids"
                ]
              },
              "example": {
                "ids": [
                  "d5d5d5d5-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rules in their new order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d5d5d5d5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Mercado Libre → equipo Providencia",
                      "enabled": true,
                      "priority": 0,
                      "match_sources": [
                        "mercadolibre"
                      ],
                      "match_channels": [],
                      "match_lead_intents": [],
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "assignee_user_ids": null,
                      "assignee_weights": {},
                      "target_vehicle_owner": false,
                      "assignment_mode": "auto_round_robin",
                      "is_portal_default": false,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies": {
      "get": {
        "summary": "List insumos",
        "description": "The clinic’s insumos with stock on hand, unit, minimum, last purchase price and supplier, plus `totals` (active, archived, below minimum). A clinic’s first open materialises the starter catalogue (general consumables) — never a migration, so an archived insumo is not handed back. `below_minimum=true` is the «Por reponer» filter.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "archived",
                "all"
              ],
              "default": "active"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "below_minimum",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Insumos + totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "name": "Guantes de examen",
                        "unit": "par",
                        "category": "Desechables",
                        "min_stock": 200,
                        "stock_on_hand": 150,
                        "below_minimum": true,
                        "last_unit_cost_clp": 198,
                        "last_purchase_on": "2026-10-05",
                        "supplier": "Dental Proveedor SpA",
                        "supplier_tax_id": "76543210-K",
                        "archived_at": null,
                        "created_at": "2026-10-01T14:00:00.000Z",
                        "updated_at": "2026-10-05T15:20:00.000Z"
                      }
                    ],
                    "totals": {
                      "active": 11,
                      "archived": 0,
                      "below_minimum": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add an insumo",
        "description": "`initial_stock` is recorded as a count so the ledger explains it. 409 when a live insumo already has that name.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "unit": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 30
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "min_stock": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 999999999
                  },
                  "supplier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "supplier_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "initial_stock": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 999999999
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Hilo dental",
                "unit": "caja",
                "category": "Insumos de box",
                "min_stock": 5,
                "initial_stock": 12
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5a5a5a5a-0000-4000-8000-000000000002",
                    "name": "Hilo dental",
                    "unit": "caja",
                    "category": "Insumos de box",
                    "min_stock": 5,
                    "stock_on_hand": 12,
                    "below_minimum": false,
                    "last_unit_cost_clp": null,
                    "last_purchase_on": null,
                    "supplier": null,
                    "supplier_tax_id": null,
                    "archived_at": null,
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-05T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/supplies/unmapped-items": {
      "get": {
        "summary": "Purchase-invoice items still asking «¿qué insumo es?»",
        "description": "Pending lines of confirmed purchase invoices, one row per (supplier, item code). The question is asked ONCE; after it is answered the same supplier item applies automatically. If a purchase feed carries totals only (no lines), nothing appears here and manual entry covers it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "responses": {
          "200": {
            "description": "Unmapped items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "supplier_key": "76543210-K",
                        "supplier_name": "Dental Proveedor SpA",
                        "item_key": "code:gn-100",
                        "item_code": "GN-100",
                        "description": "GUANTES NITRILO CAJA 100",
                        "lines": 1,
                        "total_quantity": 3,
                        "last_unit_price_clp": 9900,
                        "last_seen_on": "2026-10-05"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies/unmapped-items/resolve": {
      "post": {
        "summary": "Say which insumo a supplier item is",
        "description": "Send exactly one of `supply_id` (an existing insumo), `new_supply` (create it now) or `ignore` (not an insumo: a service, freight). Pending lines of that supplier item are applied (stock goes up by quantity × `units_per_item`) and future ones apply automatically.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "supplier_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "item_key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "units_per_item": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1000000
                  },
                  "supply_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "new_supply": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "unit": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 30
                      },
                      "category": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60
                      },
                      "min_stock": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999
                      },
                      "supplier": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "supplier_tax_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 20
                      },
                      "initial_stock": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 999999999
                      }
                    },
                    "required": [
                      "name"
                    ]
                  },
                  "ignore": {
                    "type": "boolean",
                    "enum": [
                      true
                    ]
                  }
                },
                "required": [
                  "supplier_key",
                  "item_key"
                ]
              },
              "example": {
                "supplier_key": "76543210-K",
                "item_key": "code:gn-100",
                "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                "units_per_item": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolution summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "supply": {
                      "id": "5a5a5a5a-0000-4000-8000-000000000001",
                      "name": "Guantes de examen",
                      "unit": "par",
                      "category": "Desechables",
                      "min_stock": 200,
                      "stock_on_hand": 150,
                      "below_minimum": true,
                      "last_unit_cost_clp": 198,
                      "last_purchase_on": "2026-10-05",
                      "supplier": "Dental Proveedor SpA",
                      "supplier_tax_id": "76543210-K",
                      "archived_at": null,
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-05T15:20:00.000Z"
                    },
                    "applied_lines": 1,
                    "ignored_lines": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/supplies/counts": {
      "post": {
        "summary": "Record a periodic count",
        "description": "Sets the stock of each counted insumo and records the difference as a `count` movement. All or nothing.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "counts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "supply_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "counted_quantity": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 999999999
                        }
                      },
                      "required": [
                        "supply_id",
                        "counted_quantity"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                },
                "required": [
                  "counts"
                ]
              },
              "example": {
                "counts": [
                  {
                    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "counted_quantity": 320
                  }
                ],
                "reason": "Conteo de octubre"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Movements + insumos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "results": [
                      {
                        "movement": {
                          "id": "5b5b5b5b-0000-4000-8000-000000000001",
                          "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                          "kind": "count",
                          "delta": 170,
                          "stock_after": 320,
                          "unit_cost_clp": null,
                          "reason": "Conteo de octubre",
                          "actor_user_id": null,
                          "expense_id": null,
                          "occurred_at": "2026-10-05T15:20:00.000Z",
                          "consumption": null
                        },
                        "supply": {
                          "id": "5a5a5a5a-0000-4000-8000-000000000001",
                          "name": "Guantes de examen",
                          "unit": "par",
                          "category": "Desechables",
                          "min_stock": 200,
                          "stock_on_hand": 320,
                          "below_minimum": false,
                          "last_unit_cost_clp": 198,
                          "last_purchase_on": "2026-10-05",
                          "supplier": "Dental Proveedor SpA",
                          "supplier_tax_id": "76543210-K",
                          "archived_at": null,
                          "created_at": "2026-10-01T14:00:00.000Z",
                          "updated_at": "2026-10-05T15:20:00.000Z"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/supplies/{id}": {
      "get": {
        "summary": "Get an insumo",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "name": "Guantes de examen",
                    "unit": "par",
                    "category": "Desechables",
                    "min_stock": 200,
                    "stock_on_hand": 150,
                    "below_minimum": true,
                    "last_unit_cost_clp": 198,
                    "last_purchase_on": "2026-10-05",
                    "supplier": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "archived_at": null,
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-05T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Get an insumo\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Edit an insumo",
        "description": "Name, unit, category, minimum and supplier. Stock is never edited here: it moves by movements and counts.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "unit": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 30
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "min_stock": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 999999999
                  },
                  "supplier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "supplier_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  }
                }
              },
              "example": {
                "min_stock": 300
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "name": "Guantes de examen",
                    "unit": "par",
                    "category": "Desechables",
                    "min_stock": 300,
                    "stock_on_hand": 150,
                    "below_minimum": true,
                    "last_unit_cost_clp": 198,
                    "last_purchase_on": "2026-10-05",
                    "supplier": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "archived_at": null,
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-05T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies/{id}/archive": {
      "post": {
        "summary": "Archive an insumo",
        "description": "Soft: its movements keep pointing at it and it leaves the list and the alert. Reversible with `restore`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "name": "Guantes de examen",
                    "unit": "par",
                    "category": "Desechables",
                    "min_stock": 200,
                    "stock_on_hand": 150,
                    "below_minimum": true,
                    "last_unit_cost_clp": 198,
                    "last_purchase_on": "2026-10-05",
                    "supplier": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "archived_at": "2026-10-09T12:00:00.000Z",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-05T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies/{id}/restore": {
      "post": {
        "summary": "Restore an archived insumo",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "name": "Guantes de examen",
                    "unit": "par",
                    "category": "Desechables",
                    "min_stock": 200,
                    "stock_on_hand": 150,
                    "below_minimum": true,
                    "last_unit_cost_clp": 198,
                    "last_purchase_on": "2026-10-05",
                    "supplier": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "archived_at": null,
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-05T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Restore an archived insumo\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/supplies/{id}/movements": {
      "get": {
        "summary": "An insumo’s movement history",
        "description": "Newest first: who (`actor_user_id`), when (`occurred_at`), why (`kind` + `reason`), the delta and the stock after. Cursor-paged. A `consumption` movement carries `consumption`: the prestación and the atención (cita, or the mirrored presupuesto) it came from, and `reversal` when it put the quantity back.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Movements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "5b5b5b5b-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "kind": "purchase",
                        "delta": 150,
                        "stock_after": 150,
                        "unit_cost_clp": 198,
                        "reason": "GUANTES NITRILO CAJA 100",
                        "actor_user_id": null,
                        "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                        "occurred_at": "2026-10-05T15:20:00.000Z",
                        "consumption": null
                      }
                    ],
                    "next_cursor": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record a manual entry or exit",
        "description": "An exit needs a `reason` (merma, vencimiento, préstamo…) and cannot take more than is on hand (409); a count corrects the difference.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "direction": {
                    "type": "string",
                    "enum": [
                      "entry",
                      "exit"
                    ]
                  },
                  "quantity": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 999999999
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "unit_cost_clp": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999
                  }
                },
                "required": [
                  "direction",
                  "quantity"
                ]
              },
              "example": {
                "direction": "exit",
                "quantity": 10,
                "reason": "Vencimiento"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Movement + insumo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "movement": {
                      "id": "5b5b5b5b-0000-4000-8000-000000000001",
                      "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                      "kind": "manual_exit",
                      "delta": -10,
                      "stock_after": 140,
                      "unit_cost_clp": null,
                      "reason": "Vencimiento",
                      "actor_user_id": "20000000-0000-4000-8000-000000000001",
                      "expense_id": null,
                      "occurred_at": "2026-10-05T15:20:00.000Z",
                      "consumption": null
                    },
                    "supply": {
                      "id": "5a5a5a5a-0000-4000-8000-000000000001",
                      "name": "Guantes de examen",
                      "unit": "par",
                      "category": "Desechables",
                      "min_stock": 200,
                      "stock_on_hand": 140,
                      "below_minimum": true,
                      "last_unit_cost_clp": 198,
                      "last_purchase_on": "2026-10-05",
                      "supplier": "Dental Proveedor SpA",
                      "supplier_tax_id": "76543210-K",
                      "archived_at": null,
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-05T15:20:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies/{id}/service-quantities": {
      "get": {
        "summary": "Quantity this insumo consumes per prestación",
        "description": "The optional quantities that make a performed prestación take this insumo out of stock. An insumo with none is never touched by a prestación and keeps the periodic count.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Quantities per prestación",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "5d5d5d5d-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "service_name": "Limpieza dental",
                        "quantity": 2,
                        "effective_from": "2026-10-10T09:00:00.000Z",
                        "updated_at": "2026-10-10T09:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supplies/{id}/service-quantities/{service_id}": {
      "put": {
        "summary": "Set the quantity a prestación consumes of this insumo",
        "description": "Per ONE performance of the prestación, in the insumo’s unit (a plan item of quantity 3 consumes 3×). Performing the prestación records a `consumption` movement linked to the atención; un-performing it puts the quantity back; a line is never counted twice, including mirrored (Dentalink) lines on every re-sync. A NEW quantity counts from now on: work performed before it is never consumed. Stock cannot go negative: a taking larger than the stock on hand is clamped and says so in the movement’s `reason`. 404 for an unknown insumo or prestación, 409 when the insumo is archived.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "service_id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quantity": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 999999999
                  }
                },
                "required": [
                  "quantity"
                ]
              },
              "example": {
                "quantity": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quantity",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "service_name": "Limpieza dental",
                    "quantity": 2,
                    "effective_from": "2026-10-10T09:00:00.000Z",
                    "updated_at": "2026-10-10T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Stop consuming this insumo for a prestación",
        "description": "Removes the quantity. What was already consumed stays in the history.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "service_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "removed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/suggestions": {
      "get": {
        "summary": "Suggested supplier orders from «Por reponer»",
        "description": "The insumos below their minimum, grouped by supplier, each with a suggested quantity: minimum + daily consumption (last 60 days, from the prestaciones performed) × `coverage_days`, or twice the minimum when there is no consumption, minus what is already on sent orders not yet received. `open_draft_id` is the draft «Crear pedido» would extend.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 365,
              "default": 30
            },
            "required": false,
            "name": "coverage_days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Suggestions by supplier",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "supplier_key": "76543210-K",
                        "supplier_name": "Dental Proveedor SpA",
                        "supplier_tax_id": "76543210-K",
                        "open_draft_id": null,
                        "lines": [
                          {
                            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                            "name": "Guantes de examen",
                            "unit": "par",
                            "min_stock": 200,
                            "stock_on_hand": 150,
                            "daily_consumption": 6.5,
                            "on_order": 0,
                            "last_unit_cost_clp": 198,
                            "suggested_quantity": 245
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/draft": {
      "post": {
        "summary": "Draft the orders from «Por reponer»",
        "description": "One draft per supplier with the suggested quantities (`supply_ids` narrows it). A supplier that already has an open draft gets the missing insumos added to it; quantities already on it are never overwritten. Insumos without a supplier go on a draft with no supplier name, to be named before sending. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "coverage_days": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 365,
                    "default": 30
                  },
                  "supply_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  }
                }
              },
              "example": {
                "coverage_days": 30
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Drafts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "orders": [
                      {
                        "id": "5d5d5d5d-0000-4000-8000-000000000001",
                        "number": 7,
                        "status": "draft",
                        "supplier_name": "Dental Proveedor SpA",
                        "supplier_tax_id": "76543210-K",
                        "notes": null,
                        "expected_on": "2026-10-14",
                        "sent_at": null,
                        "closed_at": null,
                        "cancelled_at": null,
                        "created_at": "2026-10-09T14:30:00.000Z",
                        "updated_at": "2026-10-12T12:00:00.000Z",
                        "lines": [
                          {
                            "id": "5e5e5e5e-0000-4000-8000-000000000001",
                            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                            "supply_name": "Guantes de examen",
                            "unit": "par",
                            "quantity_ordered": 245,
                            "quantity_received": 0,
                            "difference": -245,
                            "unit_cost_clp": 198
                          }
                        ],
                        "totals": {
                          "lines": 1,
                          "ordered": 245,
                          "received": 0,
                          "estimated_total_clp": 48510
                        },
                        "invoice": null,
                        "received_by_hand": false
                      }
                    ],
                    "nothing_to_order": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/supply-orders": {
      "get": {
        "summary": "List supplier orders",
        "description": "Newest first. `status` is `draft`, `sent`, `partially_received`, `received`, `closed` or `cancelled`; the two received states are derived from the ledger. Each order carries its lines with ordered, received and the difference.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "partially_received",
                "received",
                "closed",
                "cancelled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "5d5d5d5d-0000-4000-8000-000000000001",
                        "number": 7,
                        "status": "partially_received",
                        "supplier_name": "Dental Proveedor SpA",
                        "supplier_tax_id": "76543210-K",
                        "notes": null,
                        "expected_on": "2026-10-14",
                        "sent_at": "2026-10-09T15:00:00.000Z",
                        "closed_at": null,
                        "cancelled_at": null,
                        "created_at": "2026-10-09T14:30:00.000Z",
                        "updated_at": "2026-10-12T12:00:00.000Z",
                        "lines": [
                          {
                            "id": "5e5e5e5e-0000-4000-8000-000000000001",
                            "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                            "supply_name": "Guantes de examen",
                            "unit": "par",
                            "quantity_ordered": 300,
                            "quantity_received": 150,
                            "difference": -150,
                            "unit_cost_clp": 198
                          }
                        ],
                        "totals": {
                          "lines": 1,
                          "ordered": 300,
                          "received": 150,
                          "estimated_total_clp": 59400
                        },
                        "invoice": {
                          "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                          "document_number": "F-2210",
                          "supplier": "Dental Proveedor SpA",
                          "spent_on": "2026-10-12",
                          "amount_clp": 29700,
                          "pending_lines": 0
                        },
                        "received_by_hand": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Write a supplier order by hand",
        "description": "A draft for one supplier. 400 when an insumo does not exist or is archived. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "supplier_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "supplier_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "expected_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "supply_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 999999999
                        }
                      },
                      "required": [
                        "supply_id",
                        "quantity"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200
                  }
                },
                "required": [
                  "lines"
                ]
              },
              "example": {
                "supplier_name": "Dental Proveedor SpA",
                "supplier_tax_id": "76.543.210-K",
                "expected_on": "2026-10-14",
                "lines": [
                  {
                    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "quantity": 300
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "draft",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": null,
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/supply-orders/{id}": {
      "get": {
        "summary": "One supplier order",
        "description": "With its lines (ordered, received, difference), the purchase invoice it was received against and whether it was received by hand.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "partially_received",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": {
                      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                      "document_number": "F-2210",
                      "supplier": "Dental Proveedor SpA",
                      "spent_on": "2026-10-12",
                      "amount_clp": 29700,
                      "pending_lines": 0
                    },
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a draft order",
        "description": "Drafts only; a sent order is cancelled instead.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "removed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a draft order",
        "description": "Supplier, notes, expected date and (replacing the whole list) lines. 409 once the order was sent.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "supplier_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "supplier_tax_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "expected_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "supply_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 999999999
                        }
                      },
                      "required": [
                        "supply_id",
                        "quantity"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200
                  }
                }
              },
              "example": {
                "notes": "Entregar en recepción",
                "lines": [
                  {
                    "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                    "quantity": 300
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "draft",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": "Entregar en recepción",
                    "expected_on": "2026-10-14",
                    "sent_at": null,
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/send": {
      "post": {
        "summary": "Mark an order as sent",
        "description": "Freezes the lines. Needs a supplier name and at least one line. The PDF (`GET …/pdf`) is the sheet to send. An order never moves stock by itself: goods enter through the purchase invoice or a manual receipt, and the order reads what arrived from the ledger.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sent order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "sent",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/cancel": {
      "post": {
        "summary": "Cancel an order",
        "description": "A draft or a sent order that has received nothing. One that already received goods is closed instead (409).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "cancelled",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": "2026-10-10T12:00:00.000Z",
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/close": {
      "post": {
        "summary": "Close an order with what arrived",
        "description": "Stop waiting for the rest: a short delivery is accepted and the difference stays visible on the order. Stock is untouched.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The closed order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "closed",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": "2026-10-15T12:00:00.000Z",
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": {
                      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                      "document_number": "F-2210",
                      "supplier": "Dental Proveedor SpA",
                      "spent_on": "2026-10-12",
                      "amount_clp": 29700,
                      "pending_lines": 0
                    },
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/invoice-candidates": {
      "get": {
        "summary": "Purchase invoices an order could be received against",
        "description": "Recent invoices (180 days) with insumo lines from the same supplier or for insumos on the order, not already the receipt of another order. `pending_lines` are lines still asking «¿qué insumo es?».\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Candidate invoices",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                        "document_number": "F-2210",
                        "supplier": "Dental Proveedor SpA",
                        "spent_on": "2026-10-12",
                        "amount_clp": 29700,
                        "pending_lines": 0,
                        "matching_lines": 1
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/invoice": {
      "put": {
        "summary": "Receive an order against a purchase invoice",
        "description": "Links the invoice to the order. Writes NO movement: the invoice’s own lines already moved (or will move, once mapped) the stock, and the order reads those movements as what it received, so nothing is counted twice. 409 when the order was already received by hand, the invoice has no insumo lines or it is already another order’s receipt.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expense_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "expense_id"
                ]
              },
              "example": {
                "expense_id": "5c5c5c5c-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "partially_received",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": {
                      "expense_id": "5c5c5c5c-0000-4000-8000-000000000001",
                      "document_number": "F-2210",
                      "supplier": "Dental Proveedor SpA",
                      "spent_on": "2026-10-12",
                      "amount_clp": 29700,
                      "pending_lines": 0
                    },
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Unlink the purchase invoice from an open order",
        "description": "The invoice’s stock stays; the order simply stops reading it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "sent",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/receipts": {
      "post": {
        "summary": "Receive an order by hand",
        "description": "The goods are here and the invoice is not: each line raises the stock as an `order_receipt` movement tied to the order. 409 when the order is received against an invoice (that would count the goods twice) or is not open.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "line_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "number",
                          "exclusiveMinimum": 0,
                          "maximum": 999999999
                        },
                        "unit_cost_clp": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 999999999
                        }
                      },
                      "required": [
                        "line_id",
                        "quantity"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                },
                "required": [
                  "lines"
                ]
              },
              "example": {
                "lines": [
                  {
                    "line_id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "quantity": 150
                  }
                ],
                "note": "Guía de despacho 8841"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "number": 7,
                    "status": "partially_received",
                    "supplier_name": "Dental Proveedor SpA",
                    "supplier_tax_id": "76543210-K",
                    "notes": null,
                    "expected_on": "2026-10-14",
                    "sent_at": "2026-10-09T15:00:00.000Z",
                    "closed_at": null,
                    "cancelled_at": null,
                    "created_at": "2026-10-09T14:30:00.000Z",
                    "updated_at": "2026-10-12T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "supply_id": "5a5a5a5a-0000-4000-8000-000000000001",
                        "supply_name": "Guantes de examen",
                        "unit": "par",
                        "quantity_ordered": 300,
                        "quantity_received": 150,
                        "difference": -150,
                        "unit_cost_clp": 198
                      }
                    ],
                    "totals": {
                      "lines": 1,
                      "ordered": 300,
                      "received": 150,
                      "estimated_total_clp": 59400
                    },
                    "invoice": null,
                    "received_by_hand": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/supply-orders/{id}/pdf": {
      "get": {
        "summary": "The order as a PDF for the supplier",
        "description": "Insumo, quantity and unit, the clinic, the date and the notes; none of the clinic’s stock levels or prices.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic inventario"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/sterilization/equipment": {
      "get": {
        "summary": "List the sterilisation equipment",
        "description": "The autoclaves and equipment the clinic runs cycles on, active first. `active=true` is what the cycle form offers. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Equipment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c0e57121-0000-4000-8000-0000000004ab",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Autoclave 1",
                      "active": true,
                      "created_at": "2026-10-01T13:00:00.000Z",
                      "updated_at": "2026-10-01T13:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a piece of sterilisation equipment",
        "description": "Names are unique per clinic, ignoring case (409 otherwise). Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "required": [
                  "name"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Autoclave 1"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Equipment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000004ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Autoclave 1",
                    "active": true,
                    "created_at": "2026-10-01T13:00:00.000Z",
                    "updated_at": "2026-10-01T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/sterilization/equipment/{id}": {
      "patch": {
        "summary": "Rename or withdraw a piece of equipment",
        "description": "`active: false` withdraws it from the cycle form; its cycles keep naming it. Equipment is never deleted. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Equipment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000004ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Autoclave 1",
                    "active": false,
                    "created_at": "2026-10-01T13:00:00.000Z",
                    "updated_at": "2026-10-01T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/sterilization/cycles": {
      "get": {
        "summary": "List sterilisation cycles",
        "description": "Newest first, with the equipment name. Filter by `from` / `to` (the clinic's calendar days, inclusive), `equipment_id`, `result` and a partial `lote`. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "equipment_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "approved",
                "rejected"
              ]
            },
            "required": false,
            "name": "result",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 60
            },
            "required": false,
            "name": "lote",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Sterilisation cycles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "c0e57121-0000-4000-8000-0000000005ab",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "equipment_id": "c0e57121-0000-4000-8000-0000000004ab",
                        "equipment_name": "Autoclave 1",
                        "lote": "A-0412",
                        "run_at": "2026-10-09T17:30:00.000Z",
                        "operator_name": "Camila Rojas",
                        "result": "approved",
                        "indicator": "Indicador químico clase 5",
                        "packs": [
                          "Pack exodoncia",
                          "Pack básico"
                        ],
                        "notes": null,
                        "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                        "created_at": "2026-10-09T17:45:00.000Z",
                        "updated_at": "2026-10-09T17:45:00.000Z"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Log a sterilisation cycle",
        "description": "Records lote, `run_at`, who ran it (`operator_name`), the equipment, the `result` (`approved` / `rejected`) with the `indicator` used, and the `packs` it contained. 409 when the equipment already has that lote; 400 for a withdrawn equipment or a future `run_at`. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "equipment_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "lote": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "run_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "operator_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "result": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "indicator": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "packs": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 60
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "required": [
                  "equipment_id",
                  "lote",
                  "run_at",
                  "operator_name",
                  "result"
                ],
                "additionalProperties": false
              },
              "example": {
                "equipment_id": "c0e57121-0000-4000-8000-0000000004ab",
                "lote": "A-0412",
                "run_at": "2026-10-09T14:30:00-03:00",
                "operator_name": "Camila Rojas",
                "result": "approved",
                "indicator": "Indicador químico clase 5",
                "packs": [
                  "Pack exodoncia",
                  "Pack básico"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Sterilisation cycle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000005ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "equipment_id": "c0e57121-0000-4000-8000-0000000004ab",
                    "equipment_name": "Autoclave 1",
                    "lote": "A-0412",
                    "run_at": "2026-10-09T17:30:00.000Z",
                    "operator_name": "Camila Rojas",
                    "result": "approved",
                    "indicator": "Indicador químico clase 5",
                    "packs": [
                      "Pack exodoncia",
                      "Pack básico"
                    ],
                    "notes": null,
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-10-09T17:45:00.000Z",
                    "updated_at": "2026-10-09T17:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/sterilization/cycles/export": {
      "get": {
        "summary": "Export the cycle log as CSV or PDF",
        "description": "The same filters as the list (`from`, `to`, `equipment_id`), oldest first, for an inspection. `format=csv` is `;`-separated and BOM-prefixed for es-CL Excel; `format=pdf` is the clinic’s branded paper. Up to 5000 cycles per file (400 beyond that: narrow the dates). Requires `clinic:read`; every export is audited.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "equipment_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "pdf"
              ]
            },
            "required": true,
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV or PDF file",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/sterilization/cycles/{id}": {
      "get": {
        "summary": "Get one sterilisation cycle",
        "description": "Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Sterilisation cycle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000005ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "equipment_id": "c0e57121-0000-4000-8000-0000000004ab",
                    "equipment_name": "Autoclave 1",
                    "lote": "A-0412",
                    "run_at": "2026-10-09T17:30:00.000Z",
                    "operator_name": "Camila Rojas",
                    "result": "approved",
                    "indicator": "Indicador químico clase 5",
                    "packs": [
                      "Pack exodoncia",
                      "Pack básico"
                    ],
                    "notes": null,
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-10-09T17:45:00.000Z",
                    "updated_at": "2026-10-09T17:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Correct a sterilisation cycle",
        "description": "Any field of the cycle. A cycle is never deleted: the correction is audited with the values it replaced. 409 when the new lote is taken on that equipment. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Esterilización"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "equipment_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "lote": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "run_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "operator_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "result": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "indicator": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "packs": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 60
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "result": "rejected",
                "notes": "El indicador no virado."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sterilisation cycle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000005ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "equipment_id": "c0e57121-0000-4000-8000-0000000004ab",
                    "equipment_name": "Autoclave 1",
                    "lote": "A-0412",
                    "run_at": "2026-10-09T17:30:00.000Z",
                    "operator_name": "Camila Rojas",
                    "result": "rejected",
                    "indicator": "Indicador químico clase 5",
                    "packs": [
                      "Pack exodoncia",
                      "Pack básico"
                    ],
                    "notes": "El indicador no virado.",
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-10-09T17:45:00.000Z",
                    "updated_at": "2026-10-09T17:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurance/plans": {
      "get": {
        "summary": "List the insurer plans",
        "description": "The seguros the clinic keeps a coverage table for, active first, each with its `rule_count`. `active=true` is what the patient form offers. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Insurer plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "c0e57121-0000-4000-8000-0000000007ab",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "insurer": "Consorcio",
                        "name": "Salud Plus",
                        "active": true,
                        "rule_count": 2,
                        "created_at": "2026-10-01T13:00:00.000Z",
                        "updated_at": "2026-10-01T13:00:00.000Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add an insurer plan",
        "description": "`insurer` and `name` are unique together per clinic, ignoring case (409 otherwise). Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "insurer": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "required": [
                  "insurer",
                  "name"
                ],
                "additionalProperties": false
              },
              "example": {
                "insurer": "Consorcio",
                "name": "Salud Plus"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Insurer plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000007ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "name": "Salud Plus",
                    "active": true,
                    "rule_count": 2,
                    "created_at": "2026-10-01T13:00:00.000Z",
                    "updated_at": "2026-10-01T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/insurance/plans/{id}": {
      "patch": {
        "summary": "Rename or withdraw an insurer plan",
        "description": "`active: false` withdraws it from the patient form; patients who hold it keep it and it keeps covering. Plans are never deleted. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "insurer": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Insurer plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000007ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "name": "Salud Plus",
                    "active": false,
                    "rule_count": 2,
                    "created_at": "2026-10-01T13:00:00.000Z",
                    "updated_at": "2026-10-01T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurance/plans/{id}/coverage": {
      "get": {
        "summary": "Read a plan’s coverage table",
        "description": "The default row first, then one row per prestación by name. A `percent` row carries `percent` (0 to 100); an `amount` row carries `amount_clp` per unit; either may carry a `cap_clp` per line. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Coverage rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "c0e57121-0000-4000-8000-0000000007ac",
                        "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                        "clinic_service_id": "d0e57121-0000-4000-8000-0000000008ab",
                        "service_name": "Limpieza dental",
                        "kind": "percent",
                        "percent": 70,
                        "amount_clp": null,
                        "cap_clp": 30000
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set one coverage rule",
        "description": "Creates or replaces the plan’s row for `clinic_service_id` (omit it or send null for the plan default). `kind: percent` takes `percent` and no amount; `kind: amount` takes `amount_clp` and no percent (400 otherwise). Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_service_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "percent",
                      "amount"
                    ]
                  },
                  "percent": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100
                  },
                  "amount_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "cap_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  }
                },
                "required": [
                  "kind"
                ],
                "additionalProperties": false
              },
              "example": {
                "clinic_service_id": "d0e57121-0000-4000-8000-0000000008ab",
                "kind": "percent",
                "percent": 70,
                "cap_clp": 30000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Coverage row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000007ac",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "clinic_service_id": "d0e57121-0000-4000-8000-0000000008ab",
                    "service_name": "Limpieza dental",
                    "kind": "percent",
                    "percent": 70,
                    "amount_clp": null,
                    "cap_clp": 30000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurance/plans/{id}/coverage/{ruleId}": {
      "delete": {
        "summary": "Remove one coverage rule",
        "description": "The prestación falls back to the plan default, or to no cover. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "ruleId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/insurances": {
      "get": {
        "summary": "List a patient’s seguros",
        "description": "Primary first. Besides, never instead of, the previsión on the ficha. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Patient seguros",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "c0e57121-0000-4000-8000-0000000007ad",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                        "insurer": "Consorcio",
                        "plan_name": "Salud Plus",
                        "plan_active": true,
                        "policy_number": "P-48213",
                        "holder_name": null,
                        "valid_until": "2027-03-31",
                        "is_primary": true,
                        "created_at": "2026-10-09T17:45:00.000Z",
                        "updated_at": "2026-10-09T17:45:00.000Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a seguro to a patient",
        "description": "The patient’s first seguro is always the primary one; `is_primary: true` moves the flag from the current primary. 409 when the patient already holds that plan; 400 for a withdrawn plan. Works on any patient source: the seguro is Vitrina’s own record. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "policy_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "holder_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "is_primary": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "plan_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                "policy_number": "P-48213",
                "valid_until": "2027-03-31"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Patient seguro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000007ad",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "plan_active": true,
                    "policy_number": "P-48213",
                    "holder_name": null,
                    "valid_until": "2027-03-31",
                    "is_primary": true,
                    "created_at": "2026-10-09T17:45:00.000Z",
                    "updated_at": "2026-10-09T17:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/insurances/{insuranceId}": {
      "delete": {
        "summary": "Remove a patient’s seguro",
        "description": "Deleting the primary promotes the oldest remaining seguro. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "insuranceId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a patient’s seguro",
        "description": "Any field. `is_primary: true` makes it the primary one; to demote the primary, mark another seguro primary (`false` is a 400). Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "insuranceId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "plan_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "policy_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "holder_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "is_primary": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "valid_until": "2027-06-30"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Patient seguro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000007ad",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "plan_active": true,
                    "policy_number": "P-48213",
                    "holder_name": null,
                    "valid_until": "2027-06-30",
                    "is_primary": true,
                    "created_at": "2026-10-09T17:45:00.000Z",
                    "updated_at": "2026-10-09T17:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/coverage": {
      "get": {
        "summary": "Cobertura and copago of a presupuesto",
        "description": "Per line and in total: `price_clp`, the seguro’s estimated `coverage_clp` and the patient’s `copay_clp` (price = coverage + copay). Uses the patient’s primary seguro, or `insurance_id` as a what-if. `status` is `covered`, `no_insurance` or `insurance_expired`; without a usable seguro the copay is the full price. `totals.price_clp` is the presupuesto’s own total, so a presupuesto-level discount is already in it (`budget_discount_clp`). `id` is the budget id or its folio. Derived on read, never frozen; the same figures print on `GET /clinic/budgets/{id}/render`. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^E-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "insurance_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Budget coverage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "status": "covered",
                    "insurance": {
                      "id": "c0e57121-0000-4000-8000-0000000007ad",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                      "insurer": "Consorcio",
                      "plan_name": "Salud Plus",
                      "plan_active": true,
                      "policy_number": "P-48213",
                      "holder_name": null,
                      "valid_until": "2027-03-31",
                      "is_primary": true,
                      "created_at": "2026-10-09T17:45:00.000Z",
                      "updated_at": "2026-10-09T17:45:00.000Z"
                    },
                    "lines": [
                      {
                        "item_id": "f0e57121-0000-4000-8000-0000000009a1",
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "price_clp": 40000,
                        "coverage_clp": 28000,
                        "copay_clp": 12000,
                        "rule": "service",
                        "capped": false
                      },
                      {
                        "item_id": "f0e57121-0000-4000-8000-0000000009a2",
                        "label": "Blanqueamiento",
                        "quantity": 1,
                        "price_clp": 120000,
                        "coverage_clp": 0,
                        "copay_clp": 120000,
                        "rule": "none",
                        "capped": false
                      }
                    ],
                    "totals": {
                      "price_clp": 160000,
                      "coverage_clp": 28000,
                      "copay_clp": 132000
                    },
                    "budget_discount_clp": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/insurance-statement": {
      "get": {
        "summary": "Estado de cuenta with the seguro’s cobertura",
        "description": "The patient’s accepted presupuestos, oldest first, each with price, cobertura and copago per line and in total, and a grand total. A reissued version replaces the one it carries lines from. Payments and the outstanding balance stay the ledger’s answer (`GET /contacts/{id}/ledger`). Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "insurance_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Insurance statement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "status": "covered",
                    "insurance": {
                      "id": "c0e57121-0000-4000-8000-0000000007ad",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                      "insurer": "Consorcio",
                      "plan_name": "Salud Plus",
                      "plan_active": true,
                      "policy_number": "P-48213",
                      "holder_name": null,
                      "valid_until": "2027-03-31",
                      "is_primary": true,
                      "created_at": "2026-10-09T17:45:00.000Z",
                      "updated_at": "2026-10-09T17:45:00.000Z"
                    },
                    "budgets": [
                      {
                        "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                        "display_id": "E-12",
                        "status": "accepted",
                        "accepted_at": "2026-10-09T18:00:00.000Z",
                        "lines": [
                          {
                            "item_id": "f0e57121-0000-4000-8000-0000000009a1",
                            "label": "Limpieza dental",
                            "quantity": 1,
                            "price_clp": 40000,
                            "coverage_clp": 28000,
                            "copay_clp": 12000,
                            "rule": "service",
                            "capped": false
                          },
                          {
                            "item_id": "f0e57121-0000-4000-8000-0000000009a2",
                            "label": "Blanqueamiento",
                            "quantity": 1,
                            "price_clp": 120000,
                            "coverage_clp": 0,
                            "copay_clp": 120000,
                            "rule": "none",
                            "capped": false
                          }
                        ],
                        "totals": {
                          "price_clp": 160000,
                          "coverage_clp": 28000,
                          "copay_clp": 132000
                        },
                        "budget_discount_clp": 0,
                        "reimbursement": {
                          "claimed_clp": 28000,
                          "reimbursed_clp": 0,
                          "pending_clp": 28000
                        }
                      }
                    ],
                    "totals": {
                      "price_clp": 160000,
                      "coverage_clp": 28000,
                      "copay_clp": 132000
                    },
                    "reimbursement": {
                      "request_count": 1,
                      "requested_clp": 28000,
                      "rejected_clp": 0,
                      "reimbursed_clp": 0,
                      "reimbursed_to_clinic_clp": 0,
                      "reimbursed_to_patient_clp": 0,
                      "already_counted_clp": 0,
                      "outside_payments_clp": 0,
                      "pending_clp": 28000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/insurance-statement/render": {
      "get": {
        "summary": "Estado de cuenta with the seguro, as a PDF",
        "description": "The same figures as the JSON statement, on the clinic’s branded paper. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Seguros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "insurance_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF file",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/reimbursements": {
      "get": {
        "summary": "List reimbursement requests",
        "description": "Open requests (`to_send`, `sent`) first, the oldest atención first. Filter by `status` (comma-separated), `insurer`, `plan_id`, `patient_id`, `budget_id` or `payee`. `summary` counts every state under the non-state filters and totals what the insurers still owe (`pending_clp`), per insurer. Each item carries `reimbursed_clp` (derived), `outstanding_clp`, `shortfall_clp` and `settlement`. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 60
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "insurer",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "plan_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "budget_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "patient",
                "clinic"
              ]
            },
            "required": false,
            "name": "payee",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Reimbursement requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000a01",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "patient_name": "Paciente de ejemplo",
                        "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                        "insurer": "Consorcio",
                        "plan_name": "Salud Plus",
                        "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                        "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                        "budget_folio": "E-12",
                        "appointment_id": null,
                        "encounter_id": null,
                        "service_date": "2026-10-08",
                        "concept": "Limpieza dental",
                        "payee": "clinic",
                        "status": "sent",
                        "claimed_clp": 28000,
                        "reimbursed_clp": 0,
                        "closed_short": false,
                        "insurer_reference": null,
                        "sent_on": "2026-10-09",
                        "rejected_on": null,
                        "rejection_reason": null,
                        "settled_on": null,
                        "note": null,
                        "document_count": 2,
                        "created_at": "2026-10-09T13:00:00.000Z",
                        "updated_at": "2026-10-09T13:00:00.000Z",
                        "outstanding_clp": 28000,
                        "shortfall_clp": 0,
                        "settlement": "none",
                        "age_days": 2
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50,
                    "summary": {
                      "by_status": {
                        "to_send": {
                          "count": 0,
                          "claimed_clp": 0,
                          "reimbursed_clp": 0
                        },
                        "sent": {
                          "count": 1,
                          "claimed_clp": 28000,
                          "reimbursed_clp": 0
                        },
                        "paid": {
                          "count": 0,
                          "claimed_clp": 0,
                          "reimbursed_clp": 0
                        },
                        "rejected": {
                          "count": 0,
                          "claimed_clp": 0,
                          "reimbursed_clp": 0
                        }
                      },
                      "pending_count": 1,
                      "pending_clp": 28000,
                      "by_insurer": [
                        {
                          "insurer": "Consorcio",
                          "pending_count": 1,
                          "pending_clp": 28000
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Open a reimbursement request",
        "description": "For one atención of a patient, under their primary seguro (or `patient_insurance_id`). `claimed_clp` may be omitted when `budget_id` is sent: it then takes the estimated cobertura of the presupuesto. `payee` is who the insurer pays: `patient` (default) or `clinic`. Starts `to_send`. 400 when the patient has no seguro. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "patient_insurance_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "budget_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "service_date": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "concept": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "payee": {
                    "type": "string",
                    "enum": [
                      "patient",
                      "clinic"
                    ]
                  },
                  "claimed_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 1000000000
                  },
                  "insurer_reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "required": [
                  "patient_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                "payee": "clinic",
                "concept": "Limpieza dental"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "to_send",
                    "claimed_clp": 28000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": null,
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 2,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 28000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000c01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "kind": "boleta",
                        "title": "Boleta 4431",
                        "clinic_document_id": null,
                        "created_at": "2026-10-09T13:05:00.000Z"
                      }
                    ],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/reimbursements/{id}": {
      "get": {
        "summary": "Read a reimbursement request",
        "description": "With its `documents`, the `checklist` of boleta / bono / programa and its `allocations` (live and released). Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "sent",
                    "claimed_clp": 28000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": "2026-10-09",
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 2,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 28000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000c01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "kind": "boleta",
                        "title": "Boleta 4431",
                        "clinic_document_id": null,
                        "created_at": "2026-10-09T13:05:00.000Z"
                      }
                    ],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a reimbursement request",
        "description": "Amount, payee, atención day, concept, insurer reference, note. The amount cannot go below what is already reimbursed and the payee cannot change once payments are allocated (409). A `paid` or `rejected` request only takes notes. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "claimed_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000000000
                  },
                  "payee": {
                    "type": "string",
                    "enum": [
                      "patient",
                      "clinic"
                    ]
                  },
                  "service_date": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "concept": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "insurer_reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "claimed_clp": 30000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "sent",
                    "claimed_clp": 30000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": "2026-10-09",
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 2,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 30000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000c01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "kind": "boleta",
                        "title": "Boleta 4431",
                        "clinic_document_id": null,
                        "created_at": "2026-10-09T13:05:00.000Z"
                      }
                    ],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/reimbursements/{id}/transition": {
      "post": {
        "summary": "Send, reject or take back a request",
        "description": "`to: sent` (from `to_send` or `rejected`, with the day it went out), `to: rejected` (from `sent`, needs `reason`), `to: to_send` (take it back). 409 on any other move and while payments are allocated: `paid` is reached only by allocating an insurer payment. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string",
                    "enum": [
                      "to_send",
                      "sent",
                      "rejected"
                    ]
                  },
                  "on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "to"
                ],
                "additionalProperties": false
              },
              "example": {
                "to": "sent",
                "on": "2026-10-09"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "sent",
                    "claimed_clp": 28000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": "2026-10-09",
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 2,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 28000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000c01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "kind": "boleta",
                        "title": "Boleta 4431",
                        "clinic_document_id": null,
                        "created_at": "2026-10-09T13:05:00.000Z"
                      }
                    ],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/reimbursements/{id}/documents": {
      "post": {
        "summary": "Attach a document to a request",
        "description": "`kind` is `boleta`, `bono`, `programa` or `otro`. `clinic_document_id` optionally points at a file already on the patient’s ficha; without it the row is the checklist entry of a paper kept elsewhere. Returns the request. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "boleta",
                      "bono",
                      "programa",
                      "otro"
                    ]
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "clinic_document_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "kind"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "boleta",
                "title": "Boleta 4431"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "sent",
                    "claimed_clp": 28000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": "2026-10-09",
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 2,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 28000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000c01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "kind": "boleta",
                        "title": "Boleta 4431",
                        "clinic_document_id": null,
                        "created_at": "2026-10-09T13:05:00.000Z"
                      }
                    ],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/reimbursements/{id}/documents/{documentId}": {
      "delete": {
        "summary": "Remove a document from a request",
        "description": "Removes the checklist row, not the file on the ficha. Returns the request. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "documentId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Reimbursement request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000a01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "Paciente de ejemplo",
                    "plan_id": "c0e57121-0000-4000-8000-0000000007ab",
                    "insurer": "Consorcio",
                    "plan_name": "Salud Plus",
                    "patient_insurance_id": "c0e57121-0000-4000-8000-0000000007ad",
                    "budget_id": "e0e57121-0000-4000-8000-0000000009ab",
                    "budget_folio": "E-12",
                    "appointment_id": null,
                    "encounter_id": null,
                    "service_date": "2026-10-08",
                    "concept": "Limpieza dental",
                    "payee": "clinic",
                    "status": "sent",
                    "claimed_clp": 28000,
                    "reimbursed_clp": 0,
                    "closed_short": false,
                    "insurer_reference": null,
                    "sent_on": "2026-10-09",
                    "rejected_on": null,
                    "rejection_reason": null,
                    "settled_on": null,
                    "note": null,
                    "document_count": 0,
                    "created_at": "2026-10-09T13:00:00.000Z",
                    "updated_at": "2026-10-09T13:00:00.000Z",
                    "outstanding_clp": 28000,
                    "shortfall_clp": 0,
                    "settlement": "none",
                    "age_days": 2,
                    "documents": [],
                    "checklist": [
                      {
                        "kind": "boleta",
                        "attached": true
                      },
                      {
                        "kind": "bono",
                        "attached": false
                      },
                      {
                        "kind": "programa",
                        "attached": false
                      }
                    ],
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurer-payments": {
      "get": {
        "summary": "List insurer payments",
        "description": "Newest first, each with `allocated_clp` and `unallocated_clp`. `unallocated=true` keeps those with money left to assign. `insurers` lists the insurers the clinic has plans for. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "insurer",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "patient",
                "clinic"
              ]
            },
            "required": false,
            "name": "payee",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "unallocated",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Insurer payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000b01",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "insurer": "Consorcio",
                        "payee": "clinic",
                        "amount_clp": 28000,
                        "received_on": "2026-10-10",
                        "reference": "TRF-99231",
                        "recorded_as_payment": false,
                        "note": null,
                        "voided_at": null,
                        "void_reason": null,
                        "allocated_clp": 28000,
                        "created_at": "2026-10-10T14:00:00.000Z",
                        "unallocated_clp": 0
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50,
                    "insurers": [
                      "Consorcio"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record an insurer payment and reconcile it",
        "description": "The money an insurer paid, to the clinic or to the patient (`payee`). `allocations` settles requests in the same call, partial amounts included; the rest stays unallocated for later. A payment only settles `sent` requests of the same insurer and payee, never above what a request claimed or the payment holds (409). `recorded_as_payment` defaults to true when the practice system (Dentalink) authors that day’s money, else false; set it true when the clinic already keyed these pesos into Caja or Dentalink. A payment that matches a pago already recorded for the patient answers with `warnings`, never a refusal. This money never enters the ledger, Caja or the espejo. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "insurer": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "payee": {
                    "type": "string",
                    "enum": [
                      "patient",
                      "clinic"
                    ]
                  },
                  "amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000000000
                  },
                  "received_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "reference": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "recorded_as_payment": {
                    "type": "boolean"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "allocations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "request_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "amount_clp": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 1000000000
                        },
                        "closes_short": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "request_id",
                        "amount_clp"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "insurer",
                  "payee",
                  "amount_clp",
                  "received_on"
                ],
                "additionalProperties": false
              },
              "example": {
                "insurer": "Consorcio",
                "payee": "clinic",
                "amount_clp": 28000,
                "received_on": "2026-10-10",
                "reference": "TRF-99231",
                "allocations": [
                  {
                    "request_id": "a0e57121-0000-4000-8000-000000000a01",
                    "amount_clp": 28000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Insurer payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000b01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "payee": "clinic",
                    "amount_clp": 28000,
                    "received_on": "2026-10-10",
                    "reference": "TRF-99231",
                    "recorded_as_payment": false,
                    "note": null,
                    "voided_at": null,
                    "void_reason": null,
                    "allocated_clp": 28000,
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "unallocated_clp": 0,
                    "allocations": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000d01",
                        "payment_id": "a0e57121-0000-4000-8000-000000000b01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "amount_clp": 28000,
                        "closes_short": false,
                        "created_at": "2026-10-10T14:00:00.000Z",
                        "released_at": null,
                        "release_reason": null,
                        "patient_name": "Paciente de ejemplo",
                        "service_date": "2026-10-08",
                        "payment_received_on": "2026-10-10",
                        "payment_reference": "TRF-99231",
                        "payment_voided": false
                      }
                    ],
                    "warnings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/insurer-payments/{id}": {
      "get": {
        "summary": "Read an insurer payment",
        "description": "With its allocations (live and released). Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Insurer payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000b01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "payee": "clinic",
                    "amount_clp": 28000,
                    "received_on": "2026-10-10",
                    "reference": "TRF-99231",
                    "recorded_as_payment": false,
                    "note": null,
                    "voided_at": null,
                    "void_reason": null,
                    "allocated_clp": 28000,
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "unallocated_clp": 0,
                    "allocations": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000d01",
                        "payment_id": "a0e57121-0000-4000-8000-000000000b01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "amount_clp": 28000,
                        "closes_short": false,
                        "created_at": "2026-10-10T14:00:00.000Z",
                        "released_at": null,
                        "release_reason": null,
                        "patient_name": "Paciente de ejemplo",
                        "service_date": "2026-10-08",
                        "payment_received_on": "2026-10-10",
                        "payment_reference": "TRF-99231",
                        "payment_voided": false
                      }
                    ],
                    "warnings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurer-payments/{id}/allocations": {
      "post": {
        "summary": "Settle one more request with a payment",
        "description": "Spends `amount_clp` of the payment on a `sent` request. When the sum reaches what was claimed the request becomes `paid`; `closes_short` closes it `paid` for less. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "request_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1000000000
                  },
                  "closes_short": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "request_id",
                  "amount_clp"
                ],
                "additionalProperties": false
              },
              "example": {
                "request_id": "a0e57121-0000-4000-8000-000000000a01",
                "amount_clp": 28000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Insurer payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000b01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "payee": "clinic",
                    "amount_clp": 28000,
                    "received_on": "2026-10-10",
                    "reference": "TRF-99231",
                    "recorded_as_payment": false,
                    "note": null,
                    "voided_at": null,
                    "void_reason": null,
                    "allocated_clp": 28000,
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "unallocated_clp": 0,
                    "allocations": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000d01",
                        "payment_id": "a0e57121-0000-4000-8000-000000000b01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "amount_clp": 28000,
                        "closes_short": false,
                        "created_at": "2026-10-10T14:00:00.000Z",
                        "released_at": null,
                        "release_reason": null,
                        "patient_name": "Paciente de ejemplo",
                        "service_date": "2026-10-08",
                        "payment_received_on": "2026-10-10",
                        "payment_reference": "TRF-99231",
                        "payment_voided": false
                      }
                    ],
                    "warnings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurer-payments/{id}/allocations/{allocationId}/release": {
      "post": {
        "summary": "Release an allocation",
        "description": "Frees the pesos for another request and reopens a `paid` request as `sent`. The allocation stays on record as released, with its reason. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "allocationId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Asignado al paciente equivocado"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Insurer payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000b01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "payee": "clinic",
                    "amount_clp": 28000,
                    "received_on": "2026-10-10",
                    "reference": "TRF-99231",
                    "recorded_as_payment": false,
                    "note": null,
                    "voided_at": null,
                    "void_reason": null,
                    "allocated_clp": 0,
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "unallocated_clp": 28000,
                    "allocations": [],
                    "warnings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insurer-payments/{id}/void": {
      "post": {
        "summary": "Void an insurer payment",
        "description": "A mistake is a voided payment, never a deletion: its allocations are released and the requests they had paid reopen. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Reembolsos"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Transferencia devuelta por el banco"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Insurer payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a0e57121-0000-4000-8000-000000000b01",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "insurer": "Consorcio",
                    "payee": "clinic",
                    "amount_clp": 28000,
                    "received_on": "2026-10-10",
                    "reference": "TRF-99231",
                    "recorded_as_payment": false,
                    "note": null,
                    "voided_at": "2026-10-11T10:00:00.000Z",
                    "void_reason": "Transferencia devuelta por el banco",
                    "allocated_clp": 0,
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "unallocated_clp": 0,
                    "allocations": [
                      {
                        "id": "a0e57121-0000-4000-8000-000000000d01",
                        "payment_id": "a0e57121-0000-4000-8000-000000000b01",
                        "request_id": "a0e57121-0000-4000-8000-000000000a01",
                        "amount_clp": 28000,
                        "closes_short": false,
                        "created_at": "2026-10-10T14:00:00.000Z",
                        "released_at": null,
                        "release_reason": null,
                        "patient_name": "Paciente de ejemplo",
                        "service_date": "2026-10-08",
                        "payment_received_on": "2026-10-10",
                        "payment_reference": "TRF-99231",
                        "payment_voided": false
                      }
                    ],
                    "warnings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/status": {
      "get": {
        "summary": "Buk payroll connection status",
        "description": "One Buk (buk.cl) connection per tenant: the company subdomain plus a read-only `auth_token` API key a human mints inside Buk (Configuración → Accesos API). Every sync reads ONLY the `payroll_detail/month` operation (Buk has no company-wide aggregate endpoint) and sums the CLOSED settlements in memory — the raw per-employee response (which carries `rut`) never leaves the provider client. Readable with `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "id": "b6b6b6b6-0000-4000-8000-000000000001",
                    "subdomain": "clinicaodm",
                    "country": "chile",
                    "has_credentials": true,
                    "auth_token_preview": "a1b2••••d6",
                    "enabled": true,
                    "last_sync_at": "2026-09-22T09:05:11.000Z",
                    "last_sync_status": "ok",
                    "last_sync_error": null,
                    "created_at": "2026-08-01T13:00:00.000Z",
                    "updated_at": "2026-09-22T09:05:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/connect": {
      "post": {
        "summary": "Connect (or rotate) the tenant’s Buk API token",
        "description": "One Buk (buk.cl) connection per tenant: the company subdomain plus a read-only `auth_token` API key a human mints inside Buk (Configuración → Accesos API). Every sync reads ONLY the `payroll_detail/month` operation (Buk has no company-wide aggregate endpoint) and sums the CLOSED settlements in memory — the raw per-employee response (which carries `rut`) never leaves the provider client. Upserts BY TENANT — a second call with a new token rotates the stored credential rather than creating a second connection. The token is AES-GCM enveloped under the tenant’s own DEK before it is written; only a masked preview is ever returned.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "subdomain": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 63,
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
                  },
                  "auth_token": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 500
                  },
                  "country": {
                    "type": "string",
                    "enum": [
                      "chile"
                    ]
                  }
                },
                "required": [
                  "subdomain",
                  "auth_token"
                ]
              },
              "example": {
                "subdomain": "clinicaodm",
                "auth_token": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
                "country": "chile"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "id": "b6b6b6b6-0000-4000-8000-000000000001",
                    "subdomain": "clinicaodm",
                    "country": "chile",
                    "has_credentials": true,
                    "auth_token_preview": "a1b2••••d6",
                    "enabled": true,
                    "last_sync_at": "2026-09-22T09:05:11.000Z",
                    "last_sync_status": "ok",
                    "last_sync_error": null,
                    "created_at": "2026-08-01T13:00:00.000Z",
                    "updated_at": "2026-09-22T09:05:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/disconnect": {
      "post": {
        "summary": "Disconnect and wipe the stored Buk token",
        "description": "Disables the connection and erases the stored token — never leaves an encrypted secret behind with no way to use it. Reconnecting is `POST .../connect` again with a fresh token.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Disconnected",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": false,
                    "disconnected": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/sync": {
      "post": {
        "summary": "Sincronizar ahora — pull last month’s payroll cost from Buk",
        "description": "Runs the same aggregate pull the daily tick runs, synchronously, for the caller’s tenant. Targets the LAST FULLY-CLOSED calendar month — a liquidación Buk has not closed yet is skipped, not zeroed, and picked up by a later sync. `reason` explains a 200 that wrote nothing (`no_enabled_connection` / `no_closed_settlements`).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Sync result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "period": "2026-08",
                    "wrote": true,
                    "inserted": false,
                    "amount_clp": 5430000,
                    "settlement_count": 6,
                    "skipped_open_count": 0,
                    "skipped_unparsable_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/mapping": {
      "get": {
        "summary": "Buk mapping: professionals ↔ Buk employees and the commission concept",
        "description": "Buk stays the payroll system of record; Vitrina computes and exports, and never pays. The key Buk issued needs ítems «Lectura y Modificación», empleados «Lectura» and, to attach the liquidación PDF, «subir documentos». Each professional carries its match state and the plain next step; `ready_to_push` says whether an export can run.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "concept": {
                      "buk_item_id": 4021,
                      "buk_item_name": "Bono por comisiones"
                    },
                    "professionals": [
                      {
                        "professional_id": "b6b6b6b6-0000-4000-8000-000000000011",
                        "name": "Camila Rojas Soto",
                        "rut": "12.345.678-5",
                        "state": "confirmed",
                        "buk_employee_name": "Camila Rojas Soto",
                        "confirmed_at": "2026-10-01T14:02:00.000Z",
                        "next_action": "Listo para enviar."
                      }
                    ],
                    "counts": {
                      "confirmed": 1,
                      "suggested": 0,
                      "needs_attention": 0
                    },
                    "ready_to_push": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/mapping/match": {
      "post": {
        "summary": "Find the professionals in Buk by RUT",
        "description": "Reads Buk’s employee list in memory (RUTs are never stored) and records each RUT hit as a SUGGESTION. A confirmed match is never touched. Professionals without a match come back with a reason.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Match result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "matched": 4,
                    "unmatched": [
                      {
                        "professional_id": "b6b6b6b6-0000-4000-8000-000000000012",
                        "name": "Daniel Pino Vega",
                        "reason_code": "not_in_buk",
                        "reason": "No hay un colaborador con este RUT en Buk."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/mapping/confirm": {
      "post": {
        "summary": "Confirm suggested Buk matches",
        "description": "The owner confirms once; only confirmed matches are pushed to. Omit `professional_ids` to confirm every suggestion.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "professional_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  }
                }
              },
              "example": {
                "professional_ids": [
                  "b6b6b6b6-0000-4000-8000-000000000011"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "confirmed": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/mapping/{professionalId}": {
      "delete": {
        "summary": "Remove a professional’s Buk match",
        "description": "The professional is blocked from pushes until matched again. Items already in Buk are untouched.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "removed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/items": {
      "get": {
        "summary": "List the Buk concepts the clinic can pick",
        "description": "Buk's variable-items API, narrowed to assignable haberes with a per-person variable amount.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "responses": {
          "200": {
            "description": "Buk items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": 4021,
                        "name": "Bono por comisiones",
                        "code": "BONO_COM"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/concept": {
      "put": {
        "summary": "Choose the Buk concept for commissions",
        "description": "Maps the commission liquidación to one Buk item. Refused (422) unless the id is in `GET /clinic/hr/buk/items`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "buk_item_id": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  }
                },
                "required": [
                  "buk_item_id"
                ]
              },
              "example": {
                "buk_item_id": 4021
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Concept",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "buk_item_id": 4021,
                    "buk_item_name": "Bono por comisiones"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/hr/buk/push": {
      "post": {
        "summary": "Enviar a Buk — push a closed commission period as variable pay",
        "description": "Buk stays the payroll system of record; Vitrina computes and exports, and never pays. The key Buk issued needs ítems «Lectura y Modificación», empleados «Lectura» and, to attach the liquidación PDF, «subir documentos». One Buk assignment per professional and month (created through Buk's assignments API with `overwrite_existing_assign`), so a re-send UPDATES instead of duplicating. Each professional ends `sent`, `blocked` (not sent; `reason` says what to fix: no confirmed match, unapproved lines, $0) or `failed` (Buk refused or was unreachable; retry with `professional_ids`/`only_unsent`). A blocked professional never blocks another. The liquidación PDF is attached to the Buk employee file when Buk allows it (`document_status`); a refusal there does not un-send the item. Sent lines are marked exported.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "period": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
                  },
                  "professional_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "only_unsent": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "period"
                ]
              },
              "example": {
                "period": "2026-09"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Push result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "period": "2026-09",
                    "results": [
                      {
                        "id": "b6b6b6b6-0000-4000-8000-000000000021",
                        "idempotency_key": "b6b6b6b6-0000-4000-8000-000000000011:2026-09:commission",
                        "professional_id": "b6b6b6b6-0000-4000-8000-000000000011",
                        "professional_name": "Camila Rojas Soto",
                        "period": "2026-09",
                        "concept": "commission",
                        "amount_clp": 1250000,
                        "status": "sent",
                        "reason_code": null,
                        "reason": null,
                        "buk_employee_id": 731,
                        "buk_item_id": 4021,
                        "buk_assign_id": 90321,
                        "document_status": "attached",
                        "document_error": null,
                        "pushed_by": "00000000-0000-4000-8000-0000000000aa",
                        "pushed_at": "2026-10-02T15:30:00.000Z"
                      },
                      {
                        "id": "b6b6b6b6-0000-4000-8000-000000000022",
                        "idempotency_key": "b6b6b6b6-0000-4000-8000-000000000012:2026-09:commission",
                        "professional_id": "b6b6b6b6-0000-4000-8000-000000000012",
                        "professional_name": "Daniel Pino Vega",
                        "period": "2026-09",
                        "concept": "commission",
                        "amount_clp": 480000,
                        "status": "blocked",
                        "reason_code": "employee_not_matched",
                        "reason": "No está vinculado a un colaborador de Buk. Vincúlalo en «Vinculación con Buk».",
                        "buk_employee_id": null,
                        "buk_item_id": null,
                        "buk_assign_id": null,
                        "document_status": null,
                        "document_error": null,
                        "pushed_by": "00000000-0000-4000-8000-0000000000aa",
                        "pushed_at": "2026-10-02T15:30:00.000Z"
                      }
                    ],
                    "summary": {
                      "sent": 1,
                      "blocked": 1,
                      "failed": 0,
                      "skipped_already_sent": 0,
                      "total_sent_clp": 1250000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/hr/buk/pushes": {
      "get": {
        "summary": "Buk push log",
        "description": "Who, when, amount and Buk’s outcome per professional, newest first. `latest_only=true` collapses to the current state of each professional-month.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic HR"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": false,
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "latest_only",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Push log",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "b6b6b6b6-0000-4000-8000-000000000021",
                        "idempotency_key": "b6b6b6b6-0000-4000-8000-000000000011:2026-09:commission",
                        "professional_id": "b6b6b6b6-0000-4000-8000-000000000011",
                        "professional_name": "Camila Rojas Soto",
                        "period": "2026-09",
                        "concept": "commission",
                        "amount_clp": 1250000,
                        "status": "sent",
                        "reason_code": null,
                        "reason": null,
                        "buk_employee_id": 731,
                        "buk_item_id": 4021,
                        "buk_assign_id": 90321,
                        "document_status": "attached",
                        "document_error": null,
                        "pushed_by": "00000000-0000-4000-8000-0000000000aa",
                        "pushed_at": "2026-10-02T15:30:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/contacts/{contactId}/payments": {
      "get": {
        "summary": "A contact's payment history",
        "description": "The pagos of every patient this contact speaks for, from Vitrina’s own data only (never a live vendor read): the Dentalink / Medilink / Reservo mirror’s rows on the days the vendor owns the money, the native ledger’s on every other day — one peso is never counted twice. Newest first; `summary` covers every row, not just the page. `mirror_missing` = a connected vendor clinic whose money is not mirrored (the history is incomplete). 404 for a contact outside the tenant. `clinic_money:read`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contactId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicContactPaymentsView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "contact_id": "0199c4b6-778a-7a98-a427-8a9fea5ec5dd",
                    "source": {
                      "vendor": "dentalink",
                      "mirrored": true,
                      "mirror_enabled": true,
                      "mirror_from": "2025-10-01",
                      "last_synced_at": "2026-10-07T11:00:00.000Z"
                    },
                    "mirror_missing": false,
                    "patients": [
                      {
                        "id": "0199c4b6-77a2-752e-bbac-55e62917b0e0",
                        "name": "Camila Rojas Fuentes",
                        "relationship": "titular"
                      }
                    ],
                    "summary": {
                      "total_paid_clp": 105000,
                      "payment_count": 2,
                      "last_paid_on": "2026-10-06",
                      "outstanding_clp": 180000
                    },
                    "payments": [
                      {
                        "id": "0199c4b6-7800-7000-8000-000000000001",
                        "kind": "payment",
                        "origin": "dentalink",
                        "paid_on": "2026-10-06",
                        "amount_clp": 60000,
                        "method": "Tarjeta de débito",
                        "instrument": null,
                        "folio": "11877",
                        "voided": false,
                        "patient_id": "0199c4b6-77a2-752e-bbac-55e62917b0e0",
                        "patient_name": null,
                        "concept": "Control de ortodoncia",
                        "professional": "Dra. Paula Soto",
                        "appointment_starts_at": "2026-10-06T13:00:00.000Z",
                        "boleta": "available"
                      },
                      {
                        "id": "0199c4b6-7800-7000-8000-000000000002",
                        "kind": "refund",
                        "origin": "dentalink",
                        "paid_on": "2026-04-30",
                        "amount_clp": 15000,
                        "method": null,
                        "instrument": null,
                        "folio": null,
                        "voided": false,
                        "patient_id": "0199c4b6-77a2-752e-bbac-55e62917b0e0",
                        "patient_name": null,
                        "concept": null,
                        "professional": null,
                        "appointment_starts_at": null,
                        "boleta": "none"
                      }
                    ],
                    "has_more": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/contacts/{contactId}/payments/{paymentId}/boleta": {
      "get": {
        "summary": "A payment's boleta (PDF)",
        "description": "The boleta PDF of one row of `GET /clinic/contacts/{contactId}/payments` (the payment must be one that read returns for this contact; 404 otherwise). Native: the linked tax document. Dentalink / Medilink: resolved from the practice system on the first call and remembered (only the document id — never its link). Reservo: the venta’s boleta. The bytes are proxied; no vendor URL ever leaves the server. Errors: 404 `boleta_not_found` (the practice system has no boleta for it), 502 `boleta_pdf_unavailable`, 409 `boleta_not_supported`, 503 `reservo_session`, 401 `healthatom_permission`. `clinic_money:read`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contactId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "paymentId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The boleta PDF (`Cache-Control: private, no-store`)",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/diagnostico": {
      "get": {
        "summary": "The Diagnóstico: day N of 30, live numbers, plan band",
        "description": "The clinic's free Diagnóstico in one read. `status` is `not_started`, `active` or `closed`; `eligibility` says whether the owner can start one and, if not, why (`reason` + a printable `message`; `available_on` when this clinic's yearly Diagnóstico was already used). `window` is the open window (else the newest closed one) with `day` of `days_total`, `days_left`, and `closing.sentence` — what the last day does: a window that turned the revenue mirror on stops it and deletes the copied detail unless the clinic holds the plan; a window over a mirror that was already on never deletes. `report` is the aggregate reading (the 90 days ending today, from the same engine as `GET /clinic/insights/comercial`) — `live` while the window is open, `stored` once closed; it carries the day-one plan `band` with its price. `formula_status: propuesta` marks the formulas the mentors have not frozen yet. `history` lists past Diagnósticos. Requires `clinic_money:read` or `clinic_insights:read`. Aggregates only — counts, CLP, rates, months and professional names; never a patient, cita, presupuesto or payment. A caller WITHOUT `clinic_money:read` (a Consultor or an app on the insights scope) additionally gets every figure resting on fewer than 5 cases withheld as `null` (`suppression.min_count`) and no deletion-ledger detail.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "responses": {
          "200": {
            "description": "The Diagnóstico",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicDiagnostico"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "status": "active",
                    "viewer": {
                      "may_start": true
                    },
                    "eligibility": {
                      "can_start": false,
                      "reason": "active",
                      "message": "Tu Diagnóstico está en curso.",
                      "available_on": null
                    },
                    "window": {
                      "id": "0192f1c4-7d3a-7b21-9c4e-3f5a6b7c8d9e",
                      "status": "active",
                      "phase": "running",
                      "closing_reason": null,
                      "held_until": null,
                      "started_at": "2026-10-05T13:00:00.000Z",
                      "ends_at": "2026-11-04T13:00:00.000Z",
                      "day": 12,
                      "days_total": 30,
                      "days_left": 19,
                      "warned_at": null,
                      "ended_early_at": null,
                      "closed_at": null,
                      "outcome": null,
                      "mirror_origin": "diagnostico",
                      "closing": {
                        "deletes_detail": true,
                        "sentence": "El 4 de noviembre dejamos de copiar datos y eliminamos el detalle copiado; conservamos solo el informe con tus totales. Con el plan, todo sigue al día."
                      },
                      "runs": []
                    },
                    "sync": {
                      "state": "up_to_date",
                      "synced_at": "2026-10-16T12:03:00.000Z",
                      "message": null
                    },
                    "report": {
                      "version": 1,
                      "computed_at": "2026-10-16T12:10:00.000Z",
                      "from": "2026-07-19",
                      "to": "2026-10-16",
                      "data_through": "2026-10-16T12:03:00.000Z",
                      "state": "ready",
                      "state_reason": null,
                      "collected": {
                        "clp": 48250000,
                        "previous_clp": 44100000,
                        "delta_pct": 9.4,
                        "rule": "Lo cobrado en el período: todos los pagos recibidos, sin los anulados."
                      },
                      "acceptance": {
                        "presented": 212,
                        "accepted": 97,
                        "rate": 45.8,
                        "presented_clp": 186400000,
                        "accepted_clp": 71900000,
                        "formula_status": "acordada",
                        "rule": "Presupuestos aceptados ÷ presupuestos presentados, cada presupuesto una vez."
                      },
                      "cierres": {
                        "count": 81,
                        "value_clp": 63300000,
                        "formula_status": "acordada",
                        "rule": "Un cierre es el primer pago de un presupuesto aceptado."
                      },
                      "by_professional": [
                        {
                          "professional_id": "0192f1c4-0000-7000-8000-000000000001",
                          "professional_name": "Dra. Ejemplo",
                          "presented": 64,
                          "accepted": 35,
                          "rate": 54.7,
                          "accepted_clp": 28100000,
                          "cierres": 30,
                          "cierres_clp": 24900000,
                          "suppressed": false
                        }
                      ],
                      "months": [
                        {
                          "month": "2026-09",
                          "accepted_clp": 24300000,
                          "produced_clp": 19800000,
                          "collected_clp": 16900000,
                          "cierres": 27
                        }
                      ],
                      "first_visit": {
                        "patients": 140,
                        "paid": 61,
                        "rate": 43.6,
                        "formula_status": "acordada",
                        "rule": "De los pacientes nuevos cuya primera cita atendida cae en el período, la parte que ese día pagó más que la evaluación."
                      },
                      "second_visit": {
                        "eligible": 79,
                        "converted": 22,
                        "rate": 27.8,
                        "formula_status": "acordada",
                        "rule": "De los presupuestos aceptados del período, la parte que se aceptó en una visita posterior."
                      },
                      "botados": {
                        "unaccepted": {
                          "count": 88,
                          "clp": 74200000,
                          "suppressed": false
                        },
                        "accepted_unscheduled": {
                          "count": 0,
                          "clp": 0,
                          "suppressed": false
                        },
                        "mid_treatment_no_future_cita": {
                          "count": 23,
                          "clp": 12100000,
                          "suppressed": false
                        },
                        "total": {
                          "count": 111,
                          "clp": 86300000,
                          "suppressed": false
                        },
                        "focus": "unaccepted",
                        "formula_status": "acordada",
                        "rule": "Presupuestos de los últimos 12 meses sin cita futura: «nunca aceptó» o «quedó a medias» tras 90 días sin cita ni pago."
                      },
                      "band": {
                        "band": "M",
                        "count": 7.3,
                        "has_history": true,
                        "months": [
                          {
                            "month": "2026-07",
                            "count": 7
                          },
                          {
                            "month": "2026-08",
                            "count": 8
                          },
                          {
                            "month": "2026-09",
                            "count": 7
                          }
                        ],
                        "uf_month": 3,
                        "estimated_clp_month": 119000,
                        "price_label": "UF 3 al mes",
                        "rule": "Tu tramo se revisa cada mes con el promedio de tus últimos 3 meses; si sube, te avisamos con un mes de anticipación."
                      }
                    },
                    "report_source": "live",
                    "plan": {
                      "holds_plan": false,
                      "status": "none"
                    },
                    "history": [],
                    "suppression": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budget-followups/today": {
      "get": {
        "summary": "«Llamar hoy»: unaccepted presupuestos to follow up today",
        "description": "The clinic's follow-up of unaccepted presupuestos, one row per LINEAGE (a re-issue never restarts it). A presupuesto is on the list on the clinic day after it was first sent, and again on day 7 and day 21 (`step` 1/2/3), until it is accepted, rejected, voided, expires (60 days, 90 for ortodoncia / implantología), a cita is booked, the patient answers a touch, or reception taps «Aceptó» / «No quiere». Rows carry what reception needs to call (patient, phone, items, total, professional) and the day's logged `outcome`. When no outcome was logged and nobody at the clinic wrote to the patient since the step opened, a WhatsApp `touch` is proposed at 17:00 clinic time (11:00 when `call_list` is off) — always for a person's approval (`touch.action_id` goes to `POST /outbound/approvals/{id}/approve`); at most three per lineage; never twice a day; never a price or a discount. `counts.unlogged` is «n sin registrar». `summary` covers the lineages issued in the last 30 days: followed up vs never contacted. `template` says whether the WhatsApp template for touches outside the 24 h window is approved. Each row carries the patient's `origin` (Meta with the ad, or Orgánico: the attribution Vitrina Ads reports); narrow the list with `origin=meta|organic|unknown` and `origin_ad_id` — `counts` follow the narrowing, `summary` does not. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Today's follow-up list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "today": {
                          "type": "string"
                        },
                        "call_list": {
                          "type": "boolean"
                        },
                        "template": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "approved",
                                "pending",
                                "rejected",
                                "missing",
                                "wrong_category"
                              ]
                            },
                            "whatsapp_connected": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "name",
                            "status",
                            "whatsapp_connected"
                          ]
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "to_call": {
                              "type": "integer"
                            },
                            "logged": {
                              "type": "integer"
                            },
                            "unlogged": {
                              "type": "integer",
                              "description": "«n sin registrar»"
                            },
                            "by_priority": {
                              "type": "object",
                              "properties": {
                                "P1": {
                                  "type": "integer"
                                },
                                "P2": {
                                  "type": "integer"
                                },
                                "P3": {
                                  "type": "integer"
                                },
                                "P4": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "P1",
                                "P2",
                                "P3",
                                "P4"
                              ],
                              "description": "Leads per Prioridad de llamada, whole section."
                            },
                            "awaiting_approval": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "to_call",
                            "logged",
                            "unlogged",
                            "by_priority",
                            "awaiting_approval"
                          ]
                        },
                        "viewer": {
                          "type": "object",
                          "properties": {
                            "may_log": {
                              "type": "boolean"
                            },
                            "may_approve": {
                              "type": "boolean"
                            },
                            "may_configure": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "may_log",
                            "may_approve",
                            "may_configure"
                          ]
                        },
                        "money_visible": {
                          "type": "boolean",
                          "description": "Whether rows carry items and totals (`clinic_money:read`)."
                        },
                        "rows": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "lineage_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "source": {
                                "type": "string",
                                "enum": [
                                  "native",
                                  "mirror"
                                ],
                                "description": "`native`: a Vitrina presupuesto. `mirror`: a Dentalink / Medilink plan copied by the espejo de ingresos — it is accepted in the vendor, so there is no Vitrina accept link."
                              },
                              "budget_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid",
                                "description": "Native rows only; null on a mirror row."
                              },
                              "mirror_plan_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid",
                                "description": "Mirror rows only: the `clinic_mirror_plan` the row follows."
                              },
                              "vendor": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "dentalink",
                                  "medilink"
                                ],
                                "description": "Mirror rows only: where the plan lives («Plan en Dentalink»)."
                              },
                              "budget_display_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The folio, or the vendor plan number on a mirror row."
                              },
                              "patient_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "phone": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "contact_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "clinic_patient_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "conversation_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "linea": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "nombre": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "id",
                                  "nombre"
                                ],
                                "description": "The patient's Línea (#4008): its open lead's, else its latest."
                              },
                              "origin": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "meta",
                                      "organic",
                                      "unknown"
                                    ],
                                    "description": "`meta`: came from a Meta ad. `organic`: a linked contact with no ad touch. `unknown` («Sin dato»): no contact is linked to the person, so no ad touch can be read."
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Meta · Implantes octubre», «Meta» (ad name unknown), «Orgánico»."
                                  },
                                  "ad_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: the platform ad id (what `origin_ad_id` takes)."
                                  },
                                  "ad_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "ad_set_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "campaign_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "first_touch_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: when they first wrote through the ad (or came back)."
                                  },
                                  "via": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "first_message",
                                      "returned"
                                    ],
                                    "description": "`first_message`: the first thing they wrote came from the ad. `returned`: they were already writing and came back through one. Both count as people from ads."
                                  }
                                },
                                "required": [
                                  "kind",
                                  "label",
                                  "ad_id",
                                  "ad_name",
                                  "ad_set_name",
                                  "campaign_name",
                                  "first_touch_at",
                                  "via"
                                ],
                                "description": "The patient's Origen (null only if the read failed)."
                              },
                              "professional_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "items": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The first three item labels."
                              },
                              "item_count": {
                                "type": "integer"
                              },
                              "total_clp": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "`null` (and `items` empty) for a caller without `clinic_money:read`."
                              },
                              "issued_on": {
                                "type": "string"
                              },
                              "issued_label": {
                                "type": "string",
                                "description": "«lunes 21 de septiembre»"
                              },
                              "days_since_issue": {
                                "type": "integer"
                              },
                              "step": {
                                "anyOf": [
                                  {
                                    "type": "number",
                                    "enum": [
                                      1
                                    ]
                                  },
                                  {
                                    "type": "number",
                                    "enum": [
                                      2
                                    ]
                                  },
                                  {
                                    "type": "number",
                                    "enum": [
                                      3
                                    ]
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "The step that falls today: 1 = día siguiente, 2 = día 7, 3 = día 21."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "active",
                                  "stopped"
                                ]
                              },
                              "stop_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "accepted",
                                  "rejected",
                                  "voided",
                                  "expired",
                                  "cita_booked",
                                  "call_accepted",
                                  "call_declined",
                                  "replied",
                                  "opted_out",
                                  "no_marketing_consent",
                                  "ladder_done",
                                  "plan_paid"
                                ]
                              },
                              "touches_sent": {
                                "type": "integer"
                              },
                              "promised": {
                                "type": "boolean"
                              },
                              "outcome": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "value": {
                                    "type": "string",
                                    "enum": [
                                      "accepted",
                                      "declined",
                                      "thinking",
                                      "no_answer"
                                    ]
                                  },
                                  "at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "value",
                                  "at"
                                ]
                              },
                              "touch": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "awaiting_approval",
                                      "scheduled",
                                      "sent",
                                      "skipped",
                                      "refused",
                                      "expired",
                                      "failed"
                                    ]
                                  },
                                  "action_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "format": "uuid"
                                  },
                                  "message": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "state",
                                  "action_id",
                                  "message",
                                  "reason"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "lineage_id",
                              "source",
                              "budget_id",
                              "mirror_plan_id",
                              "vendor",
                              "budget_display_id",
                              "patient_name",
                              "phone",
                              "contact_id",
                              "clinic_patient_id",
                              "conversation_id",
                              "linea",
                              "origin",
                              "professional_name",
                              "items",
                              "item_count",
                              "total_clp",
                              "issued_on",
                              "issued_label",
                              "days_since_issue",
                              "step",
                              "status",
                              "stop_reason",
                              "touches_sent",
                              "promised",
                              "outcome",
                              "touch"
                            ]
                          }
                        },
                        "summary": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string"
                            },
                            "to": {
                              "type": "string"
                            },
                            "lineages": {
                              "type": "integer"
                            },
                            "followed_up": {
                              "type": "integer"
                            },
                            "never_contacted": {
                              "type": "integer",
                              "description": "Ended (stopped) with no call that connected and no WhatsApp sent, excluding the ones the patient accepted on their own."
                            },
                            "accepted_without_contact": {
                              "type": "integer",
                              "description": "Ended with no contact because the patient accepted on their own."
                            },
                            "in_progress": {
                              "type": "integer"
                            },
                            "closed_after_followup": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "lineages",
                            "followed_up",
                            "never_contacted",
                            "accepted_without_contact",
                            "in_progress",
                            "closed_after_followup"
                          ]
                        }
                      },
                      "required": [
                        "today",
                        "call_list",
                        "template",
                        "counts",
                        "viewer",
                        "money_visible",
                        "rows",
                        "summary"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "today": "2026-10-09",
                    "call_list": true,
                    "template": {
                      "name": "seguimiento_presupuesto",
                      "status": "approved",
                      "whatsapp_connected": true
                    },
                    "counts": {
                      "to_call": 1,
                      "logged": 0,
                      "unlogged": 1,
                      "by_priority": {
                        "P1": 1,
                        "P2": 2,
                        "P3": 3,
                        "P4": 4
                      },
                      "awaiting_approval": 0
                    },
                    "viewer": {
                      "may_log": true,
                      "may_approve": true,
                      "may_configure": false
                    },
                    "money_visible": true,
                    "rows": [
                      {
                        "id": "7b7b7b7b-0000-4000-8000-000000000001",
                        "lineage_id": "7c7c7c7c-0000-4000-8000-000000000001",
                        "source": "native",
                        "budget_id": "7d7d7d7d-0000-4000-8000-000000000001",
                        "mirror_plan_id": null,
                        "vendor": null,
                        "budget_display_id": "P-0412",
                        "patient_name": "María José Fuentes Lagos",
                        "phone": "+56960000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "conversation_id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "linea": {
                          "id": "d1d1d1d1-0000-4000-8000-000000000001",
                          "nombre": "Implantes"
                        },
                        "origin": {
                          "kind": "organic",
                          "label": "Orgánico",
                          "ad_id": null,
                          "ad_name": null,
                          "ad_set_name": null,
                          "campaign_name": null,
                          "first_touch_at": null,
                          "via": null
                        },
                        "professional_name": "Dra. Ana Soto",
                        "items": [
                          "Implante unitario"
                        ],
                        "item_count": 1,
                        "total_clp": 890000,
                        "issued_on": "2026-10-08",
                        "issued_label": "jueves 8 de octubre",
                        "days_since_issue": 1,
                        "step": 1,
                        "status": "active",
                        "stop_reason": null,
                        "touches_sent": 0,
                        "promised": false,
                        "outcome": null,
                        "touch": null
                      }
                    ],
                    "summary": {
                      "from": "2026-09-10",
                      "to": "2026-10-09",
                      "lineages": 12,
                      "followed_up": 9,
                      "never_contacted": 3,
                      "accepted_without_contact": 0,
                      "in_progress": 4,
                      "closed_after_followup": 5
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budget-followups/{id}/outcome": {
      "post": {
        "summary": "Log the outcome of a follow-up call",
        "description": "One tap of reception, ledgered against the lineage. `accepted` and `declined` stop the follow-up (and cancel a touch waiting for approval); `thinking` skips today's WhatsApp; `no_answer` lets it go out this afternoon. A later tap on the same day supersedes the earlier one — a stop tapped by mistake is undone by tapping another outcome. Never accepts the presupuesto itself. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "accepted",
                      "declined",
                      "thinking",
                      "no_answer"
                    ],
                    "description": "`accepted` «Aceptó» and `declined` «No quiere» stop the follow-up; `thinking` «Lo va a pensar» skips today's WhatsApp; `no_answer` «No contestó» sends it this afternoon."
                  }
                },
                "required": [
                  "outcome"
                ]
              },
              "example": {
                "outcome": "thinking"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated row, or null when it left the list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "lineage_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "source": {
                          "type": "string",
                          "enum": [
                            "native",
                            "mirror"
                          ],
                          "description": "`native`: a Vitrina presupuesto. `mirror`: a Dentalink / Medilink plan copied by the espejo de ingresos — it is accepted in the vendor, so there is no Vitrina accept link."
                        },
                        "budget_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "Native rows only; null on a mirror row."
                        },
                        "mirror_plan_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "Mirror rows only: the `clinic_mirror_plan` the row follows."
                        },
                        "vendor": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "dentalink",
                            "medilink"
                          ],
                          "description": "Mirror rows only: where the plan lives («Plan en Dentalink»)."
                        },
                        "budget_display_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The folio, or the vendor plan number on a mirror row."
                        },
                        "patient_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "phone": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "contact_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "clinic_patient_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "linea": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "nombre": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "nombre"
                          ],
                          "description": "The patient's Línea (#4008): its open lead's, else its latest."
                        },
                        "origin": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "kind": {
                              "type": "string",
                              "enum": [
                                "meta",
                                "organic",
                                "unknown"
                              ],
                              "description": "`meta`: came from a Meta ad. `organic`: a linked contact with no ad touch. `unknown` («Sin dato»): no contact is linked to the person, so no ad touch can be read."
                            },
                            "label": {
                              "type": "string",
                              "description": "«Meta · Implantes octubre», «Meta» (ad name unknown), «Orgánico»."
                            },
                            "ad_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Meta only: the platform ad id (what `origin_ad_id` takes)."
                            },
                            "ad_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "ad_set_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "campaign_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "first_touch_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Meta only: when they first wrote through the ad (or came back)."
                            },
                            "via": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "first_message",
                                "returned"
                              ],
                              "description": "`first_message`: the first thing they wrote came from the ad. `returned`: they were already writing and came back through one. Both count as people from ads."
                            }
                          },
                          "required": [
                            "kind",
                            "label",
                            "ad_id",
                            "ad_name",
                            "ad_set_name",
                            "campaign_name",
                            "first_touch_at",
                            "via"
                          ],
                          "description": "The patient's Origen (null only if the read failed)."
                        },
                        "professional_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "The first three item labels."
                        },
                        "item_count": {
                          "type": "integer"
                        },
                        "total_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "`null` (and `items` empty) for a caller without `clinic_money:read`."
                        },
                        "issued_on": {
                          "type": "string"
                        },
                        "issued_label": {
                          "type": "string",
                          "description": "«lunes 21 de septiembre»"
                        },
                        "days_since_issue": {
                          "type": "integer"
                        },
                        "step": {
                          "anyOf": [
                            {
                              "type": "number",
                              "enum": [
                                1
                              ]
                            },
                            {
                              "type": "number",
                              "enum": [
                                2
                              ]
                            },
                            {
                              "type": "number",
                              "enum": [
                                3
                              ]
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "The step that falls today: 1 = día siguiente, 2 = día 7, 3 = día 21."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "active",
                            "stopped"
                          ]
                        },
                        "stop_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "accepted",
                            "rejected",
                            "voided",
                            "expired",
                            "cita_booked",
                            "call_accepted",
                            "call_declined",
                            "replied",
                            "opted_out",
                            "no_marketing_consent",
                            "ladder_done",
                            "plan_paid"
                          ]
                        },
                        "touches_sent": {
                          "type": "integer"
                        },
                        "promised": {
                          "type": "boolean"
                        },
                        "outcome": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "value": {
                              "type": "string",
                              "enum": [
                                "accepted",
                                "declined",
                                "thinking",
                                "no_answer"
                              ]
                            },
                            "at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "value",
                            "at"
                          ]
                        },
                        "touch": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "awaiting_approval",
                                "scheduled",
                                "sent",
                                "skipped",
                                "refused",
                                "expired",
                                "failed"
                              ]
                            },
                            "action_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "message": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "action_id",
                            "message",
                            "reason"
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "lineage_id",
                        "source",
                        "budget_id",
                        "mirror_plan_id",
                        "vendor",
                        "budget_display_id",
                        "patient_name",
                        "phone",
                        "contact_id",
                        "clinic_patient_id",
                        "conversation_id",
                        "linea",
                        "origin",
                        "professional_name",
                        "items",
                        "item_count",
                        "total_clp",
                        "issued_on",
                        "issued_label",
                        "days_since_issue",
                        "step",
                        "status",
                        "stop_reason",
                        "touches_sent",
                        "promised",
                        "outcome",
                        "touch"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7b7b7b7b-0000-4000-8000-000000000001",
                    "lineage_id": "7c7c7c7c-0000-4000-8000-000000000001",
                    "source": "native",
                    "budget_id": "7d7d7d7d-0000-4000-8000-000000000001",
                    "mirror_plan_id": null,
                    "vendor": null,
                    "budget_display_id": "P-0412",
                    "patient_name": "María José Fuentes Lagos",
                    "phone": "+56960000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "conversation_id": "c0c0c0c0-0000-4000-8000-000000000001",
                    "linea": {
                      "id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "nombre": "Implantes"
                    },
                    "origin": {
                      "kind": "organic",
                      "label": "Orgánico",
                      "ad_id": null,
                      "ad_name": null,
                      "ad_set_name": null,
                      "campaign_name": null,
                      "first_touch_at": null,
                      "via": null
                    },
                    "professional_name": "Dra. Ana Soto",
                    "items": [
                      "Implante unitario"
                    ],
                    "item_count": 1,
                    "total_clp": 890000,
                    "issued_on": "2026-10-08",
                    "issued_label": "jueves 8 de octubre",
                    "days_since_issue": 1,
                    "step": 1,
                    "status": "active",
                    "stop_reason": null,
                    "touches_sent": 0,
                    "promised": false,
                    "outcome": {
                      "value": "thinking",
                      "at": "2026-10-09T14:12:03.000Z"
                    },
                    "touch": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budget-followups/sin-hora": {
      "get": {
        "summary": "«Llamar hoy» · «Sin hora»: open leads without a cita",
        "description": "Every OPEN lead whose contact has no cita that counts, however old (no age cut-off: dental and aesthetic demand is sporadic). A cita counts when it is confirmed or attended, or a hold still running; a cancelled or missed cita, past or future, does not — the lead is still waiting for a time. A lead leaves the list the moment a cita is booked. Sorted by call priority first (`priority` P1 to P4: P1 picked or asked for a time and was not booked, P2 wrote last and the clinic owes the reply, P3 interested, P4 cold), then lead `score` (highest first, unscored last), then the oldest last message first. Paged: `limit` (default 50, max 100) and `cursor` = the previous `next_cursor`; the cursor is a position, not an offset, so a lead leaving mid-scroll never skips another. `counts` covers the whole section, not the page: `total`, `logged` (an outcome tapped today) and `unlogged`. Each row carries what reception reads before dialling (`patient_name`, `phone`, `score`, `score_reason`, `last_message_at`) and today's `outcome`. `line` is the lead's Línea; `origin` is the lead's Origen (Meta with the ad, or Orgánico: the attribution Vitrina Ads reports). Narrow the section with `origin=meta|organic|unknown`, `origin_ad_id` and `linea_id` (a Línea id, or `none` for leads without one); `counts` follow the narrowing. Requires `clinic:read` and, for an API key or app, `clinic_patients:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50,
              "description": "Rows per page (default 50, max 100)."
            },
            "required": false,
            "description": "Rows per page (default 50, max 100).",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "`next_cursor` of the previous page."
            },
            "required": false,
            "description": "`next_cursor` of the previous page.",
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ],
              "description": "Narrow to one Línea: a clinic línea id, or `none` for leads without one. `counts` follow the narrowing."
            },
            "required": false,
            "description": "Narrow to one Línea: a clinic línea id, or `none` for leads without one. `counts` follow the narrowing.",
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^P[1-4](,P[1-4])*$",
              "description": "Only these Prioridades in `rows`, comma-separated (`P1,P2`); each set pages on its own cursor. `counts` stay the whole section."
            },
            "required": false,
            "description": "Only these Prioridades in `rows`, comma-separated (`P1,P2`); each set pages on its own cursor. `counts` stay the whole section.",
            "name": "priority",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the Sin hora list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "today": {
                          "type": "string"
                        },
                        "call_list": {
                          "type": "boolean"
                        },
                        "day_ends_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "When the clinic's day ends today (its latest closing among today's Horarios, clinic clock). `null` on a day the clinic does not open."
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer",
                              "description": "Leads in the section."
                            },
                            "logged": {
                              "type": "integer",
                              "description": "Of those, with an outcome logged today."
                            },
                            "unlogged": {
                              "type": "integer",
                              "description": "«n sin registrar»"
                            },
                            "by_priority": {
                              "type": "object",
                              "properties": {
                                "P1": {
                                  "type": "integer"
                                },
                                "P2": {
                                  "type": "integer"
                                },
                                "P3": {
                                  "type": "integer"
                                },
                                "P4": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "P1",
                                "P2",
                                "P3",
                                "P4"
                              ],
                              "description": "Leads per Prioridad de llamada, whole section."
                            },
                            "unlogged_by_priority": {
                              "type": "object",
                              "properties": {
                                "P1": {
                                  "type": "integer"
                                },
                                "P2": {
                                  "type": "integer"
                                },
                                "P3": {
                                  "type": "integer"
                                },
                                "P4": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "P1",
                                "P2",
                                "P3",
                                "P4"
                              ],
                              "description": "Of those, per Prioridad, with no outcome logged today."
                            }
                          },
                          "required": [
                            "total",
                            "logged",
                            "unlogged",
                            "by_priority",
                            "unlogged_by_priority"
                          ]
                        },
                        "rows": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "priority": {
                                "type": "string",
                                "enum": [
                                  "P1",
                                  "P2",
                                  "P3",
                                  "P4"
                                ],
                                "description": "Prioridad de llamada, from conversation facts (no model): P1 picked or asked for a time and was not booked; P2 wrote last, the clinic owes the reply; P3 interested (score > 25); P4 cold (score <= 25)."
                              },
                              "priority_fact": {
                                "type": "string",
                                "enum": [
                                  "picked_time",
                                  "asked_time",
                                  "missed_cita",
                                  "cancelled_cita",
                                  "wrote_last",
                                  "offered_unanswered",
                                  "interested",
                                  "cold"
                                ],
                                "description": "The fact that decided the tier. `missed_cita` / `cancelled_cita`: a P1 whose requested hour was booked and then missed or cancelled."
                              },
                              "priority_reason": {
                                "type": "string",
                                "description": "That fact as a Spanish sentence, e.g. «Escribió hace 3 h, le debemos respuesta»."
                              },
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The lead's id."
                              },
                              "display_id": {
                                "type": "string",
                                "description": "«L-89»."
                              },
                              "contact_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "conversation_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "patient_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "phone": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "score": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "Lead score 0–100, highest first; `null` = never scored."
                              },
                              "score_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Why it scored what it did — the line read before dialling."
                              },
                              "temperature": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "stage_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "stage_says_booked": {
                                "type": "boolean",
                                "description": "The stage says the evaluation is booked («Evaluación agendada») but no cita stands in the agenda (not under the contact, its conversations, its ficha or its WhatsApp number): check the agenda before calling."
                              },
                              "line": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Nombre of the lead's Línea; `null` when it has none."
                              },
                              "linea": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "nombre": {
                                    "type": "string"
                                  },
                                  "source": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "nombre",
                                  "source"
                                ],
                                "description": "The lead's Línea as an object (id, nombre, and why it carries it); `null` when it has none."
                              },
                              "origin": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "meta",
                                      "organic",
                                      "unknown"
                                    ],
                                    "description": "`meta`: came from a Meta ad. `organic`: a linked contact with no ad touch. `unknown` («Sin dato»): no contact is linked to the person, so no ad touch can be read."
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Meta · Implantes octubre», «Meta» (ad name unknown), «Orgánico»."
                                  },
                                  "ad_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: the platform ad id (what `origin_ad_id` takes)."
                                  },
                                  "ad_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "ad_set_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "campaign_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "first_touch_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: when they first wrote through the ad (or came back)."
                                  },
                                  "via": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "first_message",
                                      "returned"
                                    ],
                                    "description": "`first_message`: the first thing they wrote came from the ad. `returned`: they were already writing and came back through one. Both count as people from ads."
                                  }
                                },
                                "required": [
                                  "kind",
                                  "label",
                                  "ad_id",
                                  "ad_name",
                                  "ad_set_name",
                                  "campaign_name",
                                  "first_touch_at",
                                  "via"
                                ],
                                "description": "Origen of the lead (null only if the read failed)."
                              },
                              "lead_created_at": {
                                "type": "string"
                              },
                              "last_message_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "days_since_last_message": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              },
                              "outcome": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "value": {
                                    "type": "string",
                                    "enum": [
                                      "accepted",
                                      "declined",
                                      "thinking",
                                      "no_answer"
                                    ]
                                  },
                                  "at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "value",
                                  "at"
                                ],
                                "description": "Today's newest logged outcome."
                              },
                              "recontacto": {
                                "oneOf": [
                                  {
                                    "type": "object",
                                    "properties": {
                                      "state": {
                                        "type": "string",
                                        "enum": [
                                          "held"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "state"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "state": {
                                        "type": "string",
                                        "enum": [
                                          "proposed"
                                        ]
                                      },
                                      "action_id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "The outbound action waiting for approval: approve or discard it through the approvals (`/outbound/approvals`), like the inbox."
                                      },
                                      "attempt_id": {
                                        "type": "string",
                                        "format": "uuid"
                                      },
                                      "message": {
                                        "type": [
                                          "string",
                                          "null"
                                        ],
                                        "description": "The drafted WhatsApp."
                                      },
                                      "reason": {
                                        "type": [
                                          "string",
                                          "null"
                                        ],
                                        "description": "Why it is proposed."
                                      },
                                      "proposed_at": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "state",
                                      "action_id",
                                      "attempt_id",
                                      "message",
                                      "reason",
                                      "proposed_at"
                                    ]
                                  },
                                  {
                                    "type": "null"
                                  }
                                ],
                                "description": "Where this patient's Recontacto stands. `proposed`: a WhatsApp waits for a person's approval. `null`: nothing pending. `held` is no longer produced: calling never holds back a WhatsApp."
                              }
                            },
                            "required": [
                              "priority",
                              "priority_fact",
                              "priority_reason",
                              "id",
                              "display_id",
                              "contact_id",
                              "conversation_id",
                              "patient_name",
                              "phone",
                              "score",
                              "score_reason",
                              "temperature",
                              "stage_name",
                              "stage_says_booked",
                              "line",
                              "linea",
                              "origin",
                              "lead_created_at",
                              "last_message_at",
                              "days_since_last_message",
                              "outcome",
                              "recontacto"
                            ]
                          }
                        },
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "viewer": {
                          "type": "object",
                          "properties": {
                            "may_log": {
                              "type": "boolean"
                            },
                            "may_approve": {
                              "type": "boolean",
                              "description": "Whether the caller may approve or discard a proposed WhatsApp (`recontacto.state = proposed`) through the approvals."
                            }
                          },
                          "required": [
                            "may_log",
                            "may_approve"
                          ]
                        }
                      },
                      "required": [
                        "today",
                        "call_list",
                        "day_ends_at",
                        "counts",
                        "rows",
                        "next_cursor",
                        "viewer"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "today": "2026-10-09",
                    "call_list": true,
                    "day_ends_at": "2026-10-09T22:00:00.000Z",
                    "counts": {
                      "total": 10,
                      "logged": 0,
                      "unlogged": 10,
                      "by_priority": {
                        "P1": 1,
                        "P2": 2,
                        "P3": 3,
                        "P4": 4
                      },
                      "unlogged_by_priority": {
                        "P1": 1,
                        "P2": 2,
                        "P3": 3,
                        "P4": 4
                      }
                    },
                    "rows": [
                      {
                        "priority": "P2",
                        "priority_fact": "wrote_last",
                        "priority_reason": "Escribió hace 3 h, le debemos respuesta",
                        "id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "display_id": "L-89",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "conversation_id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "patient_name": "María José Fuentes Lagos",
                        "phone": "+56960000001",
                        "score": 72,
                        "score_reason": "Pidió precio de implantes y preguntó por horarios.",
                        "temperature": "warm",
                        "stage_name": "Nuevo",
                        "stage_says_booked": false,
                        "line": "Implantes",
                        "linea": {
                          "id": "d1d1d1d1-0000-4000-8000-000000000001",
                          "nombre": "Implantes",
                          "source": "keyword"
                        },
                        "origin": {
                          "kind": "organic",
                          "label": "Orgánico",
                          "ad_id": null,
                          "ad_name": null,
                          "ad_set_name": null,
                          "campaign_name": null,
                          "first_touch_at": null,
                          "via": null
                        },
                        "lead_created_at": "2026-09-20T15:02:11.000Z",
                        "last_message_at": "2026-10-09T13:40:00.000Z",
                        "days_since_last_message": 0,
                        "outcome": null,
                        "recontacto": null
                      }
                    ],
                    "next_cursor": null,
                    "viewer": {
                      "may_log": true,
                      "may_approve": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budget-followups/sin-hora/{lead_id}/outcome": {
      "post": {
        "summary": "Log the outcome of a call to a Sin hora lead",
        "description": "The same four one-tap outcomes as the presupuesto rows (`accepted`, `declined`, `thinking`, `no_answer`), recorded on the lead's timeline as a `call_outcome` activity. A later tap the same clinic day supersedes the earlier (the timeline keeps both). It records the call and cancels the WhatsApp proposed for this patient, if any. `declined` («No quiere») also closes the lead as Perdido: it moves to its pipeline's lost stage with «No quiere» as the reason, leaves Sin hora and is never chased by Recontacto again; moving it back to a stage undoes it. Returns the updated row, or null when the lead left the section (a cita was booked, or «No quiere»). A pipeline with no lost stage refuses `declined`. 404 when the lead is not open. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "lead_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "accepted",
                      "declined",
                      "thinking",
                      "no_answer"
                    ],
                    "description": "`accepted` «Aceptó» and `declined` «No quiere» stop the follow-up; `thinking` «Lo va a pensar» skips today's WhatsApp; `no_answer` «No contestó» sends it this afternoon."
                  }
                },
                "required": [
                  "outcome"
                ]
              },
              "example": {
                "outcome": "no_answer"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated row, or null when it left the list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "priority": {
                          "type": "string",
                          "enum": [
                            "P1",
                            "P2",
                            "P3",
                            "P4"
                          ],
                          "description": "Prioridad de llamada, from conversation facts (no model): P1 picked or asked for a time and was not booked; P2 wrote last, the clinic owes the reply; P3 interested (score > 25); P4 cold (score <= 25)."
                        },
                        "priority_fact": {
                          "type": "string",
                          "enum": [
                            "picked_time",
                            "asked_time",
                            "missed_cita",
                            "cancelled_cita",
                            "wrote_last",
                            "offered_unanswered",
                            "interested",
                            "cold"
                          ],
                          "description": "The fact that decided the tier. `missed_cita` / `cancelled_cita`: a P1 whose requested hour was booked and then missed or cancelled."
                        },
                        "priority_reason": {
                          "type": "string",
                          "description": "That fact as a Spanish sentence, e.g. «Escribió hace 3 h, le debemos respuesta»."
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The lead's id."
                        },
                        "display_id": {
                          "type": "string",
                          "description": "«L-89»."
                        },
                        "contact_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "patient_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "phone": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "score": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "description": "Lead score 0–100, highest first; `null` = never scored."
                        },
                        "score_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Why it scored what it did — the line read before dialling."
                        },
                        "temperature": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "stage_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "stage_says_booked": {
                          "type": "boolean",
                          "description": "The stage says the evaluation is booked («Evaluación agendada») but no cita stands in the agenda (not under the contact, its conversations, its ficha or its WhatsApp number): check the agenda before calling."
                        },
                        "line": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Nombre of the lead's Línea; `null` when it has none."
                        },
                        "linea": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "nombre": {
                              "type": "string"
                            },
                            "source": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "id",
                            "nombre",
                            "source"
                          ],
                          "description": "The lead's Línea as an object (id, nombre, and why it carries it); `null` when it has none."
                        },
                        "origin": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "kind": {
                              "type": "string",
                              "enum": [
                                "meta",
                                "organic",
                                "unknown"
                              ],
                              "description": "`meta`: came from a Meta ad. `organic`: a linked contact with no ad touch. `unknown` («Sin dato»): no contact is linked to the person, so no ad touch can be read."
                            },
                            "label": {
                              "type": "string",
                              "description": "«Meta · Implantes octubre», «Meta» (ad name unknown), «Orgánico»."
                            },
                            "ad_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Meta only: the platform ad id (what `origin_ad_id` takes)."
                            },
                            "ad_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "ad_set_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "campaign_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "first_touch_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Meta only: when they first wrote through the ad (or came back)."
                            },
                            "via": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "first_message",
                                "returned"
                              ],
                              "description": "`first_message`: the first thing they wrote came from the ad. `returned`: they were already writing and came back through one. Both count as people from ads."
                            }
                          },
                          "required": [
                            "kind",
                            "label",
                            "ad_id",
                            "ad_name",
                            "ad_set_name",
                            "campaign_name",
                            "first_touch_at",
                            "via"
                          ],
                          "description": "Origen of the lead (null only if the read failed)."
                        },
                        "lead_created_at": {
                          "type": "string"
                        },
                        "last_message_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "days_since_last_message": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "outcome": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "value": {
                              "type": "string",
                              "enum": [
                                "accepted",
                                "declined",
                                "thinking",
                                "no_answer"
                              ]
                            },
                            "at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "value",
                            "at"
                          ],
                          "description": "Today's newest logged outcome."
                        },
                        "recontacto": {
                          "oneOf": [
                            {
                              "type": "object",
                              "properties": {
                                "state": {
                                  "type": "string",
                                  "enum": [
                                    "held"
                                  ]
                                }
                              },
                              "required": [
                                "state"
                              ]
                            },
                            {
                              "type": "object",
                              "properties": {
                                "state": {
                                  "type": "string",
                                  "enum": [
                                    "proposed"
                                  ]
                                },
                                "action_id": {
                                  "type": "string",
                                  "format": "uuid",
                                  "description": "The outbound action waiting for approval: approve or discard it through the approvals (`/outbound/approvals`), like the inbox."
                                },
                                "attempt_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "message": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The drafted WhatsApp."
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "Why it is proposed."
                                },
                                "proposed_at": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "state",
                                "action_id",
                                "attempt_id",
                                "message",
                                "reason",
                                "proposed_at"
                              ]
                            },
                            {
                              "type": "null"
                            }
                          ],
                          "description": "Where this patient's Recontacto stands. `proposed`: a WhatsApp waits for a person's approval. `null`: nothing pending. `held` is no longer produced: calling never holds back a WhatsApp."
                        }
                      },
                      "required": [
                        "priority",
                        "priority_fact",
                        "priority_reason",
                        "id",
                        "display_id",
                        "contact_id",
                        "conversation_id",
                        "patient_name",
                        "phone",
                        "score",
                        "score_reason",
                        "temperature",
                        "stage_name",
                        "stage_says_booked",
                        "line",
                        "linea",
                        "origin",
                        "lead_created_at",
                        "last_message_at",
                        "days_since_last_message",
                        "outcome",
                        "recontacto"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "priority": "P2",
                    "priority_fact": "wrote_last",
                    "priority_reason": "Escribió hace 3 h, le debemos respuesta",
                    "id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "display_id": "L-89",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "conversation_id": "c0c0c0c0-0000-4000-8000-000000000001",
                    "patient_name": "María José Fuentes Lagos",
                    "phone": "+56960000001",
                    "score": 72,
                    "score_reason": "Pidió precio de implantes y preguntó por horarios.",
                    "temperature": "warm",
                    "stage_name": "Nuevo",
                    "stage_says_booked": false,
                    "line": "Implantes",
                    "linea": {
                      "id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "nombre": "Implantes",
                      "source": "keyword"
                    },
                    "origin": {
                      "kind": "organic",
                      "label": "Orgánico",
                      "ad_id": null,
                      "ad_name": null,
                      "ad_set_name": null,
                      "campaign_name": null,
                      "first_touch_at": null,
                      "via": null
                    },
                    "lead_created_at": "2026-09-20T15:02:11.000Z",
                    "last_message_at": "2026-10-09T13:40:00.000Z",
                    "days_since_last_message": 0,
                    "outcome": {
                      "value": "no_answer",
                      "at": "2026-10-09T14:12:03.000Z"
                    },
                    "recontacto": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/lineas": {
      "get": {
        "summary": "List the clinic's Líneas, with the mapping and the unmapped",
        "description": "A Línea is one entry in the clinic's own short list of what patients come for (Implantes, Estética, Endodoncia, TTM…), usually fewer than its specialties. The list is seeded from the roster's specialties on first read and is editable. Every roster specialty and every ad maps into exactly one Línea: `specialties` and `rules` (name fragments an ad's campaign, ad set or ad name must contain) say how; `keywords` are the words a lead without an ad says when it wants the Línea (seeded for dental clinics, editable); `unmapped_specialties` and the `ads` with `linea_id: null` are the ones the owner still has to place. Each lead carries exactly one Línea (`linea` on the lead). Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "responses": {
          "200": {
            "description": "Líneas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicLineasOverview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "lineas": [
                      {
                        "id": "5b1f0c1e-0000-4000-8000-000000000001",
                        "nombre": "Implantes",
                        "is_default": false,
                        "archived": false,
                        "leads": 41,
                        "open_leads": 33,
                        "rules": [
                          {
                            "id": "5b1f0c1e-0000-4000-8000-000000000003",
                            "match": "implant",
                            "source": "seed"
                          }
                        ],
                        "keywords": [
                          {
                            "id": "5b1f0c1e-0000-4000-8000-000000000005",
                            "keyword": "implant",
                            "source": "seed"
                          }
                        ],
                        "specialties": [
                          {
                            "id": "5b1f0c1e-0000-4000-8000-000000000002",
                            "nombre": "Implantología"
                          }
                        ]
                      }
                    ],
                    "unmapped_specialties": [
                      {
                        "id": "5b1f0c1e-0000-4000-8000-000000000004",
                        "nombre": "Prostodoncia"
                      }
                    ],
                    "ads": [
                      {
                        "external_id": "120250735360390233",
                        "campaign_name": "Campaña de mensajes IMPLANTES",
                        "ad_set_name": "INTERESES",
                        "ad_name": "Implantes 2",
                        "linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
                        "via": "rule",
                        "matched": "implant",
                        "conversations": 119,
                        "leads": 38
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a Línea",
        "description": "Find-or-create by name (accent- and case-insensitive). Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  }
                },
                "required": [
                  "nombre"
                ]
              },
              "example": {
                "nombre": "Implantes"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Línea",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5b1f0c1e-0000-4000-8000-000000000001",
                    "tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
                    "nombre": "Implantes",
                    "is_default": false,
                    "archived_at": null,
                    "created_at": "2026-10-09T12:00:00.000Z",
                    "updated_at": "2026-10-09T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/lineas/{id}": {
      "patch": {
        "summary": "Rename or archive a Línea",
        "description": "An archived Línea leaves the pickers but keeps the leads that carry it. The default Línea («General») cannot be archived. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "archived": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "nombre": "Implantología"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Línea",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5b1f0c1e-0000-4000-8000-000000000001",
                    "tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
                    "nombre": "Implantes",
                    "is_default": false,
                    "archived_at": null,
                    "created_at": "2026-10-09T12:00:00.000Z",
                    "updated_at": "2026-10-09T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lineas/{id}/merge": {
      "post": {
        "summary": "Merge a Línea into another",
        "description": "Moves every lead, specialty and rule of this Línea to `into_id`, then removes it. Irreversible. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "into_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "into_id"
                ]
              },
              "example": {
                "into_id": "5b1f0c1e-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "into": {
                      "id": "5b1f0c1e-0000-4000-8000-000000000001",
                      "tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
                      "nombre": "Implantes",
                      "is_default": false,
                      "archived_at": null,
                      "created_at": "2026-10-09T12:00:00.000Z",
                      "updated_at": "2026-10-09T12:00:00.000Z"
                    },
                    "moved": {
                      "leads": 4,
                      "specialties": 1,
                      "rules": 2
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/lineas/specialties/{id}": {
      "put": {
        "summary": "Place a roster specialty in a Línea",
        "description": "`id` is a `clinic_specialty` id. `linea_id: null` takes it out of its Línea. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "linea_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "`null` takes the specialty out of its Línea (unmapped)."
                  }
                },
                "required": [
                  "linea_id"
                ]
              },
              "example": {
                "linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Placed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5b1f0c1e-0000-4000-8000-000000000002",
                    "linea_id": "5b1f0c1e-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lineas/{id}/rules": {
      "post": {
        "summary": "Map ads to a Línea by a name fragment",
        "description": "An ad whose campaign, ad set or ad name contains the fragment belongs to this Línea; the longest fragment wins. A fragment already pointing at another Línea answers 409. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "match": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80,
                    "description": "A fragment of a campaign, ad set or ad name (accent- and case-insensitive), e.g. `IMPLANT` or an ad set named after a professional."
                  }
                },
                "required": [
                  "match"
                ]
              },
              "example": {
                "match": "GABRIELA RIQUELME"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5b1f0c1e-0000-4000-8000-000000000003",
                    "tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
                    "linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
                    "match": "GABRIELA RIQUELME",
                    "source": "owner",
                    "created_at": "2026-10-09T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lineas/rules/{id}": {
      "delete": {
        "summary": "Remove a name fragment rule",
        "description": "Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/lineas/{id}/keywords": {
      "post": {
        "summary": "Add a keyword for leads without an ad",
        "description": "A lead that did not come from an ad gets its Línea from its own words before it falls back to General: the lead's title, the booked cita's tratamiento, the score reason, the conversation summary and the notes are matched (accent- and case-insensitive, from the start of a word) against each Línea's keywords. The first of those fields that names a Línea decides; within it the Línea with the most keywords wins. An ad's Línea and a person's choice are never replaced. No model call. A keyword already pointing at another Línea answers 409. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keyword": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 80,
                    "description": "A word or phrase a lead without an ad says when it wants this Línea (accent- and case-insensitive, matched from the start of a word: `implant` finds «implantes»). E.g. `botox`."
                  }
                },
                "required": [
                  "keyword"
                ]
              },
              "example": {
                "keyword": "botox"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Keyword",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5b1f0c1e-0000-4000-8000-000000000005",
                    "tenant_id": "5b1f0c1e-0000-4000-8000-0000000000aa",
                    "linea_id": "5b1f0c1e-0000-4000-8000-000000000001",
                    "keyword": "botox",
                    "source": "owner",
                    "created_at": "2026-10-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lineas/keywords/{id}": {
      "delete": {
        "summary": "Remove a keyword",
        "description": "Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lines"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/evaluation": {
      "get": {
        "summary": "The clinic's «Evaluación»",
        "description": "Clinic data the per-agent price policy reads: whether the clinic charges the evaluation (`declared: false` while it never said), a single amount (`null` = each specialty's evaluation prestación price, summarised in `default_amount`), whether it is waived when the patient starts treatment, and the convenios with a free evaluation. It reaches an agent's prompt only when that agent's `behavior_policies.treatment_price` is set. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "responses": {
          "200": {
            "description": "The evaluation settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "declared": {
                          "type": "boolean",
                          "description": "False while the clinic never said whether it charges the evaluation."
                        },
                        "charged": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "amount_clp": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "waived_on_treatment_start": {
                          "type": "boolean"
                        },
                        "free_agreements": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ]
                          },
                          "description": "The convenios with a free evaluation that still exist."
                        },
                        "default_amount": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "min_clp": {
                              "type": "integer"
                            },
                            "max_clp": {
                              "type": "integer"
                            },
                            "services": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "nombre": {
                                    "type": "string"
                                  },
                                  "categoria": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "precio_clp": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "id",
                                  "nombre",
                                  "categoria",
                                  "precio_clp"
                                ]
                              },
                              "maxItems": 20
                            },
                            "service_count": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "min_clp",
                            "max_clp",
                            "services",
                            "service_count"
                          ],
                          "description": "What `amount_clp: null` means for this clinic: the priced evaluation prestaciones (`is_entry`) in the aranceles the agent quotes, cheapest first (at most 20 listed). Null when none is priced."
                        },
                        "agreements": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ]
                          },
                          "description": "The clinic's active convenios — the options for `free_agreement_ids`."
                        }
                      },
                      "required": [
                        "declared",
                        "charged",
                        "amount_clp",
                        "waived_on_treatment_start",
                        "free_agreements",
                        "default_amount",
                        "agreements"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "declared": true,
                    "charged": true,
                    "amount_clp": null,
                    "waived_on_treatment_start": true,
                    "free_agreements": [
                      {
                        "id": "7c1e2f4a-0000-4000-8000-000000000001",
                        "name": "Convenio Club Deportivo"
                      }
                    ],
                    "default_amount": {
                      "min_clp": 15000,
                      "max_clp": 50000,
                      "services": [
                        {
                          "id": "5d2b9e10-0000-4000-8000-000000000001",
                          "nombre": "Evaluación medicina estética facial",
                          "categoria": null,
                          "precio_clp": 15000
                        },
                        {
                          "id": "5d2b9e10-0000-4000-8000-000000000002",
                          "nombre": "Evaluación y diagnóstico dental inicial",
                          "categoria": null,
                          "precio_clp": 50000
                        }
                      ],
                      "service_count": 2
                    },
                    "agreements": [
                      {
                        "id": "7c1e2f4a-0000-4000-8000-000000000001",
                        "name": "Convenio Club Deportivo"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update the clinic's «Evaluación»",
        "description": "Partial: merges field by field over what is stored. `free_agreement_ids` must be convenios of this workspace (400 otherwise). Returns the same view as the GET. Audited. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "charged": {
                    "type": "boolean",
                    "description": "Whether the clinic charges the evaluation."
                  },
                  "amount_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000,
                    "description": "One amount in whole CLP for every evaluation. Null = each specialty's evaluation prestación (`is_entry`) price from the catalogue."
                  },
                  "waived_on_treatment_start": {
                    "type": "boolean",
                    "description": "«No se cobra si inicia tratamiento»: the evaluation is credited to the treatment when the patient starts it."
                  },
                  "free_agreement_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200,
                    "description": "Convenios (`clinic_agreement` ids) whose members get the evaluation free."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "charged": true,
                "waived_on_treatment_start": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated evaluation settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "declared": {
                          "type": "boolean",
                          "description": "False while the clinic never said whether it charges the evaluation."
                        },
                        "charged": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "amount_clp": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "waived_on_treatment_start": {
                          "type": "boolean"
                        },
                        "free_agreements": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ]
                          },
                          "description": "The convenios with a free evaluation that still exist."
                        },
                        "default_amount": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "min_clp": {
                              "type": "integer"
                            },
                            "max_clp": {
                              "type": "integer"
                            },
                            "services": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "nombre": {
                                    "type": "string"
                                  },
                                  "categoria": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "precio_clp": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "id",
                                  "nombre",
                                  "categoria",
                                  "precio_clp"
                                ]
                              },
                              "maxItems": 20
                            },
                            "service_count": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "min_clp",
                            "max_clp",
                            "services",
                            "service_count"
                          ],
                          "description": "What `amount_clp: null` means for this clinic: the priced evaluation prestaciones (`is_entry`) in the aranceles the agent quotes, cheapest first (at most 20 listed). Null when none is priced."
                        },
                        "agreements": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "name"
                            ]
                          },
                          "description": "The clinic's active convenios — the options for `free_agreement_ids`."
                        }
                      },
                      "required": [
                        "declared",
                        "charged",
                        "amount_clp",
                        "waived_on_treatment_start",
                        "free_agreements",
                        "default_amount",
                        "agreements"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "declared": true,
                    "charged": true,
                    "amount_clp": null,
                    "waived_on_treatment_start": true,
                    "free_agreements": [
                      {
                        "id": "7c1e2f4a-0000-4000-8000-000000000001",
                        "name": "Convenio Club Deportivo"
                      }
                    ],
                    "default_amount": {
                      "min_clp": 15000,
                      "max_clp": 50000,
                      "services": [
                        {
                          "id": "5d2b9e10-0000-4000-8000-000000000001",
                          "nombre": "Evaluación medicina estética facial",
                          "categoria": null,
                          "precio_clp": 15000
                        },
                        {
                          "id": "5d2b9e10-0000-4000-8000-000000000002",
                          "nombre": "Evaluación y diagnóstico dental inicial",
                          "categoria": null,
                          "precio_clp": 50000
                        }
                      ],
                      "service_count": 2
                    },
                    "agreements": [
                      {
                        "id": "7c1e2f4a-0000-4000-8000-000000000001",
                        "name": "Convenio Club Deportivo"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/price-policy-test": {
      "get": {
        "summary": "The agent’s price-policy test and its readout",
        "description": "A per-conversation A/B of the agent’s `behavior_policies.treatment_price` (#3313), healthcare agents only (`available: false` otherwise). Nothing runs until the owner starts it. `running` is the live test, `last_ended` the latest ended one (records are kept). Per arm: conversations assigned (every NEW conversation, at its first turn with this agent, by a stable hash), hours booked, attended, paid, and treatments accepted, counted from rows within `outcome_window_days` of the conversation opening. `price_asked` / `evaluation_offered` are null, with `text_signals.reason`, while the clinic has not accepted the data-processing agreement that covers conversation signals. Counts only: no winner, no probabilities. Requires `ai_agents:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The test view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "available": {
                          "type": "boolean"
                        },
                        "published_policy": {
                          "type": "string",
                          "enum": [
                            "unset",
                            "first_ask",
                            "never"
                          ]
                        },
                        "proposed_arms": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "a": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ]
                            },
                            "b": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ]
                            }
                          },
                          "required": [
                            "a",
                            "b"
                          ]
                        },
                        "policy_labels": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "running": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "experiment_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "started_at": {
                              "type": "string"
                            },
                            "ended_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "days_elapsed": {
                              "type": "integer"
                            },
                            "approved_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "approval_note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "outcome_window_days": {
                              "type": "integer"
                            },
                            "text_signals": {
                              "type": "object",
                              "properties": {
                                "available": {
                                  "type": "boolean"
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "available",
                                "reason"
                              ]
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "treatment_price": {
                                    "type": "string",
                                    "enum": [
                                      "unset",
                                      "first_ask",
                                      "never"
                                    ]
                                  },
                                  "policy_label": {
                                    "type": "string"
                                  },
                                  "kept": {
                                    "type": "boolean"
                                  },
                                  "counts": {
                                    "type": "object",
                                    "properties": {
                                      "assigned": {
                                        "type": "integer"
                                      },
                                      "price_asked": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "evaluation_offered": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "signals_measured": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "booked": {
                                        "type": "integer"
                                      },
                                      "attended": {
                                        "type": "integer"
                                      },
                                      "paid": {
                                        "type": "integer"
                                      },
                                      "treatment_accepted": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "assigned",
                                      "price_asked",
                                      "evaluation_offered",
                                      "signals_measured",
                                      "booked",
                                      "attended",
                                      "paid",
                                      "treatment_accepted"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "treatment_price",
                                  "policy_label",
                                  "kept",
                                  "counts"
                                ]
                              }
                            },
                            "definitions": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "help": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "help"
                                ]
                              }
                            }
                          },
                          "required": [
                            "experiment_id",
                            "started_at",
                            "ended_at",
                            "days_elapsed",
                            "approved_at",
                            "approval_note",
                            "outcome_window_days",
                            "text_signals",
                            "arms",
                            "definitions"
                          ]
                        },
                        "last_ended": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "experiment_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "started_at": {
                              "type": "string"
                            },
                            "ended_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "days_elapsed": {
                              "type": "integer"
                            },
                            "approved_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "approval_note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "outcome_window_days": {
                              "type": "integer"
                            },
                            "text_signals": {
                              "type": "object",
                              "properties": {
                                "available": {
                                  "type": "boolean"
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "available",
                                "reason"
                              ]
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "treatment_price": {
                                    "type": "string",
                                    "enum": [
                                      "unset",
                                      "first_ask",
                                      "never"
                                    ]
                                  },
                                  "policy_label": {
                                    "type": "string"
                                  },
                                  "kept": {
                                    "type": "boolean"
                                  },
                                  "counts": {
                                    "type": "object",
                                    "properties": {
                                      "assigned": {
                                        "type": "integer"
                                      },
                                      "price_asked": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "evaluation_offered": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "signals_measured": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "booked": {
                                        "type": "integer"
                                      },
                                      "attended": {
                                        "type": "integer"
                                      },
                                      "paid": {
                                        "type": "integer"
                                      },
                                      "treatment_accepted": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "assigned",
                                      "price_asked",
                                      "evaluation_offered",
                                      "signals_measured",
                                      "booked",
                                      "attended",
                                      "paid",
                                      "treatment_accepted"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "treatment_price",
                                  "policy_label",
                                  "kept",
                                  "counts"
                                ]
                              }
                            },
                            "definitions": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "help": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "help"
                                ]
                              }
                            }
                          },
                          "required": [
                            "experiment_id",
                            "started_at",
                            "ended_at",
                            "days_elapsed",
                            "approved_at",
                            "approval_note",
                            "outcome_window_days",
                            "text_signals",
                            "arms",
                            "definitions"
                          ]
                        },
                        "total_tests": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "available",
                        "published_policy",
                        "proposed_arms",
                        "policy_labels",
                        "running",
                        "last_ended",
                        "total_tests"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "published_policy": "first_ask",
                    "proposed_arms": {
                      "a": "first_ask",
                      "b": "never"
                    },
                    "policy_labels": {
                      "unset": "Sin definir (sus instrucciones, sin regla agregada)",
                      "first_ask": "Precio en la primera pregunta",
                      "never": "Nunca dar el precio del tratamiento"
                    },
                    "running": {
                      "experiment_id": "0199a1b2-0000-7000-8000-000000000001",
                      "started_at": "2026-10-01T13:00:00.000Z",
                      "ended_at": null,
                      "days_elapsed": 12,
                      "approved_at": "2026-10-01T13:00:00.000Z",
                      "approval_note": "Iniciada por <owner> el 2026-10-01 13:00 UTC. Brazo A: Precio en la primera pregunta. Brazo B: Nunca dar el precio del tratamiento. 50/50 por conversación nueva.",
                      "outcome_window_days": 30,
                      "text_signals": {
                        "available": false,
                        "reason": "Para contar quiénes preguntaron el precio y quiénes llegaron a la oferta de evaluación, la clínica debe aceptar el acuerdo de tratamiento de datos actualizado. El resto de la prueba se mide igual."
                      },
                      "arms": [
                        {
                          "key": "control",
                          "label": "A",
                          "treatment_price": "first_ask",
                          "policy_label": "Precio en la primera pregunta",
                          "kept": false,
                          "counts": {
                            "assigned": 64,
                            "price_asked": null,
                            "evaluation_offered": null,
                            "signals_measured": null,
                            "booked": 9,
                            "attended": 6,
                            "paid": 5,
                            "treatment_accepted": 2
                          }
                        },
                        {
                          "key": "variant_b",
                          "label": "B",
                          "treatment_price": "never",
                          "policy_label": "Nunca dar el precio del tratamiento",
                          "kept": false,
                          "counts": {
                            "assigned": 61,
                            "price_asked": null,
                            "evaluation_offered": null,
                            "signals_measured": null,
                            "booked": 7,
                            "attended": 5,
                            "paid": 4,
                            "treatment_accepted": 1
                          }
                        }
                      ],
                      "definitions": [
                        {
                          "key": "assigned",
                          "label": "Conversaciones en la prueba",
                          "help": "…"
                        },
                        {
                          "key": "booked",
                          "label": "Agendaron una hora",
                          "help": "…"
                        }
                      ]
                    },
                    "last_ended": null,
                    "total_tests": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/price-policy-test/start": {
      "post": {
        "summary": "Start the agent’s price-policy test",
        "description": "The owner’s explicit start; the approval (who, when, note) is recorded on the experiment. Arm A is the published policy and arm B the other one; with `treatment_price: unset` the caller names both arms. 50/50, conversations opened from now on only. 409 `already_running` / `not_healthcare`. Audited. Requires `ai_agents:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "arms": {
                    "type": "object",
                    "properties": {
                      "a": {
                        "type": "string",
                        "enum": [
                          "unset",
                          "first_ask",
                          "never"
                        ]
                      },
                      "b": {
                        "type": "string",
                        "enum": [
                          "unset",
                          "first_ask",
                          "never"
                        ]
                      }
                    },
                    "required": [
                      "a",
                      "b"
                    ],
                    "additionalProperties": false,
                    "description": "Required only when the agent’s published `treatment_price` is `unset` (two distinct values). Otherwise arm A is the published policy and arm B the other one; when sent, it must match."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "The owner’s note, recorded with the approval."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "note": "Probamos 4 semanas con la recepción avisada."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The test view, now running",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "available": {
                          "type": "boolean"
                        },
                        "published_policy": {
                          "type": "string",
                          "enum": [
                            "unset",
                            "first_ask",
                            "never"
                          ]
                        },
                        "proposed_arms": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "a": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ]
                            },
                            "b": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ]
                            }
                          },
                          "required": [
                            "a",
                            "b"
                          ]
                        },
                        "policy_labels": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "running": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "experiment_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "started_at": {
                              "type": "string"
                            },
                            "ended_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "days_elapsed": {
                              "type": "integer"
                            },
                            "approved_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "approval_note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "outcome_window_days": {
                              "type": "integer"
                            },
                            "text_signals": {
                              "type": "object",
                              "properties": {
                                "available": {
                                  "type": "boolean"
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "available",
                                "reason"
                              ]
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "treatment_price": {
                                    "type": "string",
                                    "enum": [
                                      "unset",
                                      "first_ask",
                                      "never"
                                    ]
                                  },
                                  "policy_label": {
                                    "type": "string"
                                  },
                                  "kept": {
                                    "type": "boolean"
                                  },
                                  "counts": {
                                    "type": "object",
                                    "properties": {
                                      "assigned": {
                                        "type": "integer"
                                      },
                                      "price_asked": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "evaluation_offered": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "signals_measured": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "booked": {
                                        "type": "integer"
                                      },
                                      "attended": {
                                        "type": "integer"
                                      },
                                      "paid": {
                                        "type": "integer"
                                      },
                                      "treatment_accepted": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "assigned",
                                      "price_asked",
                                      "evaluation_offered",
                                      "signals_measured",
                                      "booked",
                                      "attended",
                                      "paid",
                                      "treatment_accepted"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "treatment_price",
                                  "policy_label",
                                  "kept",
                                  "counts"
                                ]
                              }
                            },
                            "definitions": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "help": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "help"
                                ]
                              }
                            }
                          },
                          "required": [
                            "experiment_id",
                            "started_at",
                            "ended_at",
                            "days_elapsed",
                            "approved_at",
                            "approval_note",
                            "outcome_window_days",
                            "text_signals",
                            "arms",
                            "definitions"
                          ]
                        },
                        "last_ended": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "experiment_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "started_at": {
                              "type": "string"
                            },
                            "ended_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "days_elapsed": {
                              "type": "integer"
                            },
                            "approved_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "approval_note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "outcome_window_days": {
                              "type": "integer"
                            },
                            "text_signals": {
                              "type": "object",
                              "properties": {
                                "available": {
                                  "type": "boolean"
                                },
                                "reason": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "available",
                                "reason"
                              ]
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "treatment_price": {
                                    "type": "string",
                                    "enum": [
                                      "unset",
                                      "first_ask",
                                      "never"
                                    ]
                                  },
                                  "policy_label": {
                                    "type": "string"
                                  },
                                  "kept": {
                                    "type": "boolean"
                                  },
                                  "counts": {
                                    "type": "object",
                                    "properties": {
                                      "assigned": {
                                        "type": "integer"
                                      },
                                      "price_asked": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "evaluation_offered": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "signals_measured": {
                                        "type": [
                                          "integer",
                                          "null"
                                        ]
                                      },
                                      "booked": {
                                        "type": "integer"
                                      },
                                      "attended": {
                                        "type": "integer"
                                      },
                                      "paid": {
                                        "type": "integer"
                                      },
                                      "treatment_accepted": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "assigned",
                                      "price_asked",
                                      "evaluation_offered",
                                      "signals_measured",
                                      "booked",
                                      "attended",
                                      "paid",
                                      "treatment_accepted"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "treatment_price",
                                  "policy_label",
                                  "kept",
                                  "counts"
                                ]
                              }
                            },
                            "definitions": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "help": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "help"
                                ]
                              }
                            }
                          },
                          "required": [
                            "experiment_id",
                            "started_at",
                            "ended_at",
                            "days_elapsed",
                            "approved_at",
                            "approval_note",
                            "outcome_window_days",
                            "text_signals",
                            "arms",
                            "definitions"
                          ]
                        },
                        "total_tests": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "available",
                        "published_policy",
                        "proposed_arms",
                        "policy_labels",
                        "running",
                        "last_ended",
                        "total_tests"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "published_policy": "first_ask",
                    "proposed_arms": {
                      "a": "first_ask",
                      "b": "never"
                    },
                    "policy_labels": {
                      "unset": "Sin definir (sus instrucciones, sin regla agregada)",
                      "first_ask": "Precio en la primera pregunta",
                      "never": "Nunca dar el precio del tratamiento"
                    },
                    "running": {
                      "experiment_id": "0199a1b2-0000-7000-8000-000000000001",
                      "started_at": "2026-10-01T13:00:00.000Z",
                      "ended_at": null,
                      "days_elapsed": 12,
                      "approved_at": "2026-10-01T13:00:00.000Z",
                      "approval_note": "Iniciada por <owner> el 2026-10-01 13:00 UTC. Brazo A: Precio en la primera pregunta. Brazo B: Nunca dar el precio del tratamiento. 50/50 por conversación nueva.",
                      "outcome_window_days": 30,
                      "text_signals": {
                        "available": false,
                        "reason": "Para contar quiénes preguntaron el precio y quiénes llegaron a la oferta de evaluación, la clínica debe aceptar el acuerdo de tratamiento de datos actualizado. El resto de la prueba se mide igual."
                      },
                      "arms": [
                        {
                          "key": "control",
                          "label": "A",
                          "treatment_price": "first_ask",
                          "policy_label": "Precio en la primera pregunta",
                          "kept": false,
                          "counts": {
                            "assigned": 64,
                            "price_asked": null,
                            "evaluation_offered": null,
                            "signals_measured": null,
                            "booked": 9,
                            "attended": 6,
                            "paid": 5,
                            "treatment_accepted": 2
                          }
                        },
                        {
                          "key": "variant_b",
                          "label": "B",
                          "treatment_price": "never",
                          "policy_label": "Nunca dar el precio del tratamiento",
                          "kept": false,
                          "counts": {
                            "assigned": 61,
                            "price_asked": null,
                            "evaluation_offered": null,
                            "signals_measured": null,
                            "booked": 7,
                            "attended": 5,
                            "paid": 4,
                            "treatment_accepted": 1
                          }
                        }
                      ],
                      "definitions": [
                        {
                          "key": "assigned",
                          "label": "Conversaciones en la prueba",
                          "help": "…"
                        },
                        {
                          "key": "booked",
                          "label": "Agendaron una hora",
                          "help": "…"
                        }
                      ]
                    },
                    "last_ended": null,
                    "total_tests": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/price-policy-test/end": {
      "post": {
        "summary": "End the test and keep one policy",
        "description": "Stops assignment (conversations already in the test return to the published policy) and keeps the record. When the kept policy differs from the published one it is staged on the agent’s draft (`staged: true`); publish it with `POST /ai-agents/{id}/publish`. `other_draft_changes: true` warns that the draft held other unpublished changes, which ship with that publish. Audited. Requires `ai_agents:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keep": {
                    "type": "string",
                    "enum": [
                      "unset",
                      "first_ask",
                      "never"
                    ],
                    "description": "The policy to keep: one of the two arms."
                  }
                },
                "required": [
                  "keep"
                ],
                "additionalProperties": false
              },
              "example": {
                "keep": "never"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The ended test",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "view": {
                          "type": "object",
                          "properties": {
                            "available": {
                              "type": "boolean"
                            },
                            "published_policy": {
                              "type": "string",
                              "enum": [
                                "unset",
                                "first_ask",
                                "never"
                              ]
                            },
                            "proposed_arms": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "a": {
                                  "type": "string",
                                  "enum": [
                                    "unset",
                                    "first_ask",
                                    "never"
                                  ]
                                },
                                "b": {
                                  "type": "string",
                                  "enum": [
                                    "unset",
                                    "first_ask",
                                    "never"
                                  ]
                                }
                              },
                              "required": [
                                "a",
                                "b"
                              ]
                            },
                            "policy_labels": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            "running": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "experiment_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "started_at": {
                                  "type": "string"
                                },
                                "ended_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "days_elapsed": {
                                  "type": "integer"
                                },
                                "approved_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "approval_note": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "outcome_window_days": {
                                  "type": "integer"
                                },
                                "text_signals": {
                                  "type": "object",
                                  "properties": {
                                    "available": {
                                      "type": "boolean"
                                    },
                                    "reason": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "available",
                                    "reason"
                                  ]
                                },
                                "arms": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "key": {
                                        "type": "string"
                                      },
                                      "label": {
                                        "type": "string"
                                      },
                                      "treatment_price": {
                                        "type": "string",
                                        "enum": [
                                          "unset",
                                          "first_ask",
                                          "never"
                                        ]
                                      },
                                      "policy_label": {
                                        "type": "string"
                                      },
                                      "kept": {
                                        "type": "boolean"
                                      },
                                      "counts": {
                                        "type": "object",
                                        "properties": {
                                          "assigned": {
                                            "type": "integer"
                                          },
                                          "price_asked": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "evaluation_offered": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "signals_measured": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "booked": {
                                            "type": "integer"
                                          },
                                          "attended": {
                                            "type": "integer"
                                          },
                                          "paid": {
                                            "type": "integer"
                                          },
                                          "treatment_accepted": {
                                            "type": "integer"
                                          }
                                        },
                                        "required": [
                                          "assigned",
                                          "price_asked",
                                          "evaluation_offered",
                                          "signals_measured",
                                          "booked",
                                          "attended",
                                          "paid",
                                          "treatment_accepted"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "key",
                                      "label",
                                      "treatment_price",
                                      "policy_label",
                                      "kept",
                                      "counts"
                                    ]
                                  }
                                },
                                "definitions": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "key": {
                                        "type": "string"
                                      },
                                      "label": {
                                        "type": "string"
                                      },
                                      "help": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "key",
                                      "label",
                                      "help"
                                    ]
                                  }
                                }
                              },
                              "required": [
                                "experiment_id",
                                "started_at",
                                "ended_at",
                                "days_elapsed",
                                "approved_at",
                                "approval_note",
                                "outcome_window_days",
                                "text_signals",
                                "arms",
                                "definitions"
                              ]
                            },
                            "last_ended": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "experiment_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "started_at": {
                                  "type": "string"
                                },
                                "ended_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "days_elapsed": {
                                  "type": "integer"
                                },
                                "approved_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "approval_note": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "outcome_window_days": {
                                  "type": "integer"
                                },
                                "text_signals": {
                                  "type": "object",
                                  "properties": {
                                    "available": {
                                      "type": "boolean"
                                    },
                                    "reason": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "available",
                                    "reason"
                                  ]
                                },
                                "arms": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "key": {
                                        "type": "string"
                                      },
                                      "label": {
                                        "type": "string"
                                      },
                                      "treatment_price": {
                                        "type": "string",
                                        "enum": [
                                          "unset",
                                          "first_ask",
                                          "never"
                                        ]
                                      },
                                      "policy_label": {
                                        "type": "string"
                                      },
                                      "kept": {
                                        "type": "boolean"
                                      },
                                      "counts": {
                                        "type": "object",
                                        "properties": {
                                          "assigned": {
                                            "type": "integer"
                                          },
                                          "price_asked": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "evaluation_offered": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "signals_measured": {
                                            "type": [
                                              "integer",
                                              "null"
                                            ]
                                          },
                                          "booked": {
                                            "type": "integer"
                                          },
                                          "attended": {
                                            "type": "integer"
                                          },
                                          "paid": {
                                            "type": "integer"
                                          },
                                          "treatment_accepted": {
                                            "type": "integer"
                                          }
                                        },
                                        "required": [
                                          "assigned",
                                          "price_asked",
                                          "evaluation_offered",
                                          "signals_measured",
                                          "booked",
                                          "attended",
                                          "paid",
                                          "treatment_accepted"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "key",
                                      "label",
                                      "treatment_price",
                                      "policy_label",
                                      "kept",
                                      "counts"
                                    ]
                                  }
                                },
                                "definitions": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "key": {
                                        "type": "string"
                                      },
                                      "label": {
                                        "type": "string"
                                      },
                                      "help": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "key",
                                      "label",
                                      "help"
                                    ]
                                  }
                                }
                              },
                              "required": [
                                "experiment_id",
                                "started_at",
                                "ended_at",
                                "days_elapsed",
                                "approved_at",
                                "approval_note",
                                "outcome_window_days",
                                "text_signals",
                                "arms",
                                "definitions"
                              ]
                            },
                            "total_tests": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "available",
                            "published_policy",
                            "proposed_arms",
                            "policy_labels",
                            "running",
                            "last_ended",
                            "total_tests"
                          ]
                        },
                        "kept": {
                          "type": "string",
                          "enum": [
                            "unset",
                            "first_ask",
                            "never"
                          ]
                        },
                        "staged": {
                          "type": "boolean"
                        },
                        "other_draft_changes": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "view",
                        "kept",
                        "staged",
                        "other_draft_changes"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "view": {
                      "available": true,
                      "published_policy": "first_ask",
                      "proposed_arms": {
                        "a": "first_ask",
                        "b": "never"
                      },
                      "policy_labels": {
                        "unset": "Sin definir (sus instrucciones, sin regla agregada)",
                        "first_ask": "Precio en la primera pregunta",
                        "never": "Nunca dar el precio del tratamiento"
                      },
                      "running": null,
                      "last_ended": {
                        "experiment_id": "0199a1b2-0000-7000-8000-000000000001",
                        "started_at": "2026-10-01T13:00:00.000Z",
                        "ended_at": null,
                        "days_elapsed": 12,
                        "approved_at": "2026-10-01T13:00:00.000Z",
                        "approval_note": "Iniciada por <owner> el 2026-10-01 13:00 UTC. Brazo A: Precio en la primera pregunta. Brazo B: Nunca dar el precio del tratamiento. 50/50 por conversación nueva.",
                        "outcome_window_days": 30,
                        "text_signals": {
                          "available": false,
                          "reason": "Para contar quiénes preguntaron el precio y quiénes llegaron a la oferta de evaluación, la clínica debe aceptar el acuerdo de tratamiento de datos actualizado. El resto de la prueba se mide igual."
                        },
                        "arms": [
                          {
                            "key": "control",
                            "label": "A",
                            "treatment_price": "first_ask",
                            "policy_label": "Precio en la primera pregunta",
                            "kept": false,
                            "counts": {
                              "assigned": 64,
                              "price_asked": null,
                              "evaluation_offered": null,
                              "signals_measured": null,
                              "booked": 9,
                              "attended": 6,
                              "paid": 5,
                              "treatment_accepted": 2
                            }
                          },
                          {
                            "key": "variant_b",
                            "label": "B",
                            "treatment_price": "never",
                            "policy_label": "Nunca dar el precio del tratamiento",
                            "kept": false,
                            "counts": {
                              "assigned": 61,
                              "price_asked": null,
                              "evaluation_offered": null,
                              "signals_measured": null,
                              "booked": 7,
                              "attended": 5,
                              "paid": 4,
                              "treatment_accepted": 1
                            }
                          }
                        ],
                        "definitions": [
                          {
                            "key": "assigned",
                            "label": "Conversaciones en la prueba",
                            "help": "…"
                          },
                          {
                            "key": "booked",
                            "label": "Agendaron una hora",
                            "help": "…"
                          }
                        ]
                      },
                      "total_tests": 1
                    },
                    "kept": "never",
                    "staged": true,
                    "other_draft_changes": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/cash-sessions/current": {
      "get": {
        "summary": "The open caja session at a sucursal",
        "description": "The shift at `location_id`, with `running_expected_clp` — what the LEDGER says should be in the drawer right now (opening float + cash taken, reversed payments excluded) — and the takings split by instrument. `session: null` is a 200 and the ordinary morning state, not a 404. Only EFECTIVO counts toward the expectation: a card or a transfer taken at the counter belongs to the shift but never reaches the till.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "location_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The open session, or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "session": {
                      "id": "d1d832c4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                      "opened_at": "2026-09-22T22:21:16.863Z",
                      "opening_float_clp": 30000,
                      "closed_by_user_id": null,
                      "closed_at": null,
                      "expected_clp": null,
                      "counted_clp": null,
                      "difference_clp": null,
                      "difference_reason": null,
                      "status": "open",
                      "note": "Turno mañana",
                      "created_at": "2026-09-22T22:21:16.863Z",
                      "updated_at": "2026-09-22T22:21:16.863Z",
                      "location_name": "Sucursal Maipú",
                      "running_expected_clp": 30000,
                      "collected_clp": 0,
                      "payments": 0,
                      "by_instrument": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/cash-sessions/open": {
      "get": {
        "summary": "List every open caja session",
        "description": "One per sucursal at most — a partial unique index enforces it. What the sidebar badge reads.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "responses": {
          "200": {
            "description": "Open sessions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "d1d832c4-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "opened_at": "2026-09-22T22:21:16.863Z",
                        "opening_float_clp": 30000,
                        "closed_by_user_id": null,
                        "closed_at": null,
                        "expected_clp": null,
                        "counted_clp": null,
                        "difference_clp": null,
                        "difference_reason": null,
                        "status": "open",
                        "note": "Turno mañana",
                        "created_at": "2026-09-22T22:21:16.863Z",
                        "updated_at": "2026-09-22T22:21:16.863Z",
                        "location_name": "Sucursal Maipú"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/cash-sessions": {
      "get": {
        "summary": "List caja sessions",
        "description": "The shift history, newest first, each with its expected vs counted and the stored difference (SIGNED: negative is short, positive is over) plus the reason somebody wrote for it. `from`/`to` are inclusive calendar days over the day the drawer was OPENED, so a late close that crossed midnight stays with the shift it belongs to.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed",
                "reconciled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Sessions + totals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "d1d832c4-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "opened_at": "2026-09-22T22:21:16.863Z",
                        "opening_float_clp": 30000,
                        "closed_by_user_id": null,
                        "closed_at": null,
                        "expected_clp": null,
                        "counted_clp": null,
                        "difference_clp": null,
                        "difference_reason": null,
                        "status": "open",
                        "note": "Turno mañana",
                        "created_at": "2026-09-22T22:21:16.863Z",
                        "updated_at": "2026-09-22T22:21:16.863Z",
                        "location_name": "Sucursal Maipú",
                        "running_expected_clp": 30000,
                        "collected_clp": 0,
                        "payments": 0,
                        "by_instrument": []
                      }
                    ],
                    "count": 1,
                    "difference_clp": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Open the drawer",
        "description": "One open session per sucursal, enforced by a partial unique index — a second attempt answers 409 rather than creating a parallel shift. Requires a signed-in user: `opened_by_user_id` is NOT NULL because «quién abrió la caja» is the point of a shift, so an api_key principal is refused rather than stored as a null nobody can chase.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "opening_float_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "location_id"
                ]
              },
              "example": {
                "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                "opening_float_clp": 30000,
                "note": "Turno mañana"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Caja session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d1d832c4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "opened_at": "2026-09-22T22:21:16.863Z",
                    "opening_float_clp": 30000,
                    "closed_by_user_id": null,
                    "closed_at": null,
                    "expected_clp": null,
                    "counted_clp": null,
                    "difference_clp": null,
                    "difference_reason": null,
                    "status": "open",
                    "note": "Turno mañana",
                    "created_at": "2026-09-22T22:21:16.863Z",
                    "updated_at": "2026-09-22T22:21:16.863Z",
                    "location_name": "Sucursal Maipú",
                    "running_expected_clp": 30000,
                    "collected_clp": 0,
                    "payments": 0,
                    "by_instrument": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/cash-sessions/{id}": {
      "get": {
        "summary": "One caja session and its payments",
        "description": "The shift plus every payment stamped with it, grouped by instrument in `by_instrument` and listed in `items`. A payment is stamped at RECORD TIME while the session is open (payments design §10.1) and never back-filled — back-filling would move money between two already-counted shifts.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "responses": {
          "200": {
            "description": "Caja session + payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d1d832c4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "opened_at": "2026-09-22T22:21:16.863Z",
                    "opening_float_clp": 30000,
                    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_at": "2026-09-22T22:21:17.127Z",
                    "expected_clp": 30000,
                    "counted_clp": 50000,
                    "difference_clp": 20000,
                    "difference_reason": "Vuelto de un pago en efectivo",
                    "status": "closed",
                    "note": "Turno mañana",
                    "created_at": "2026-09-22T22:21:16.863Z",
                    "updated_at": "2026-09-22T22:21:17.127Z",
                    "location_name": "Sucursal Maipú",
                    "running_expected_clp": 30000,
                    "collected_clp": 0,
                    "payments": 0,
                    "by_instrument": [],
                    "items": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/cash-sessions/{id}/close": {
      "post": {
        "summary": "Close the drawer against a count",
        "description": "⚠ THE BODY CARRIES NO `expected_clp`, AND CANNOT. The server computes the expectation FROM THE LEDGER (opening float + cash stamped with the session, reversed excluded) and returns the difference; a screen that could post its own expectation could hide a shortfall. A non-zero difference REQUIRES a written reason — refused in the service and by a CHECK.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "counted_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "difference_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "counted_clp"
                ]
              },
              "example": {
                "counted_clp": 50000,
                "difference_reason": "Vuelto de un pago en efectivo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Closed session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d1d832c4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "opened_at": "2026-09-22T22:21:16.863Z",
                    "opening_float_clp": 30000,
                    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_at": "2026-09-22T22:21:17.127Z",
                    "expected_clp": 30000,
                    "counted_clp": 50000,
                    "difference_clp": 20000,
                    "difference_reason": "Vuelto de un pago en efectivo",
                    "status": "closed",
                    "note": "Turno mañana",
                    "created_at": "2026-09-22T22:21:16.863Z",
                    "updated_at": "2026-09-22T22:21:17.127Z",
                    "location_name": "Sucursal Maipú",
                    "running_expected_clp": 30000,
                    "collected_clp": 0,
                    "payments": 0,
                    "by_instrument": [],
                    "items": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/cash-sessions/{id}/reconcile": {
      "post": {
        "summary": "Sign off a counted shift",
        "description": "The supervisory acceptance of a shift that has already been counted. Only a `closed` session can be reconciled.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Cash"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                }
              },
              "example": {
                "note": "Revisado con el arqueo del turno"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reconciled session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d1d832c4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "opened_at": "2026-09-22T22:21:16.863Z",
                    "opening_float_clp": 30000,
                    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_at": "2026-09-22T22:21:17.127Z",
                    "expected_clp": 30000,
                    "counted_clp": 50000,
                    "difference_clp": 20000,
                    "difference_reason": "Vuelto de un pago en efectivo",
                    "status": "reconciled",
                    "note": "Revisado con el arqueo del turno",
                    "created_at": "2026-09-22T22:21:16.863Z",
                    "updated_at": "2026-09-22T22:21:17.146Z",
                    "location_name": "Sucursal Maipú",
                    "running_expected_clp": 30000,
                    "collected_clp": 0,
                    "payments": 0,
                    "by_instrument": [],
                    "items": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/commissions/history": {
      "get": {
        "summary": "A professional’s commission history, Vitrina and Dentalink",
        "description": "One professional’s months, newest first, so the whole history is in one place: for each month `settled_by` (`vitrina` from «primer mes que liquida Vitrina» on, `dentalink` before it), Vitrina’s own lines (`native`: counts and Σ, `read_only: false`), the liquidaciones Dentalink settled and the migration imported (`imported`, labelled «Importada de Dentalink», `read_only: true`, Dentalink’s own total) and the Dentalink exports the clinic uploaded for pages the reader could not reach (`exports`, `read_only: true`). Imported months are never recomputed, approved or exported and never count as months Vitrina computed. 404 unknown professional. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic caja"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Commission history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "professional_id": "0192f0c4-0000-7000-8000-000000000001",
                    "first_native_money_month": "2026-11",
                    "months": [
                      {
                        "period": "2026-11",
                        "settled_by": "vitrina",
                        "native": {
                          "source": "vitrina",
                          "read_only": false,
                          "lines": 42,
                          "amount_clp": 1260000,
                          "computed": 0,
                          "approved": 42,
                          "exported": 0
                        },
                        "imported": [],
                        "exports": []
                      },
                      {
                        "period": "2026-10",
                        "settled_by": "dentalink",
                        "native": null,
                        "imported": [
                          {
                            "id": "0192f0c4-0000-7000-8000-000000000002",
                            "source": "dentalink_import",
                            "label": "Importada de Dentalink",
                            "read_only": true,
                            "external_id": "9001",
                            "location_id": null,
                            "location_name": "Central",
                            "period_from": "2026-10-01",
                            "period_to": "2026-10-31",
                            "total_clp": 1180000,
                            "lines_total_clp": 1180000,
                            "deductions_clp": -80000,
                            "line_count": 38,
                            "imported_at": "2026-11-05T14:00:00.000Z"
                          }
                        ],
                        "exports": []
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/commissions/imported/{id}": {
      "get": {
        "summary": "One liquidación imported from Dentalink, with its lines",
        "description": "A liquidación as Dentalink settled it (read-only, «Importada de Dentalink»): dentist, sucursal, period, Dentalink’s total, the page’s own total and deductions, and its lines (day, prestación, base, rate in basis points, deductions, amount). The paciente is the migrated ficha’s id when resolvable and the INITIALS — never a name, like the native liquidación PDF. 404 unknown id. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic caja"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Imported liquidación",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0192f0c4-0000-7000-8000-000000000002",
                    "source": "dentalink_import",
                    "label": "Importada de Dentalink",
                    "read_only": true,
                    "professional_id": "0192f0c4-0000-7000-8000-000000000001",
                    "external_id": "9001",
                    "location_id": "0192f0c4-0000-7000-8000-000000000003",
                    "location_name": "Central",
                    "period": "2026-10",
                    "period_from": "2026-10-01",
                    "period_to": "2026-10-31",
                    "total_clp": 152000,
                    "lines_total_clp": 152000,
                    "deductions_clp": -80000,
                    "line_count": 2,
                    "imported_at": "2026-11-05T14:00:00.000Z",
                    "lines": [
                      {
                        "position": 0,
                        "performed_on": "2026-10-02",
                        "treatment": "Obturación resina",
                        "patient": {
                          "id": "0192f0c4-0000-7000-8000-000000000004",
                          "initials": "APE"
                        },
                        "base_clp": 60000,
                        "rate_bps": 4000,
                        "deductions_clp": 0,
                        "amount_clp": 24000
                      },
                      {
                        "position": 1,
                        "performed_on": "2026-10-15",
                        "treatment": "Corona metal porcelana",
                        "patient": {
                          "id": null,
                          "initials": "BFD"
                        },
                        "base_clp": 400000,
                        "rate_bps": 4000,
                        "deductions_clp": -80000,
                        "amount_clp": 128000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/commissions/imported-exports": {
      "post": {
        "summary": "Upload Dentalink’s own export of a liquidación",
        "description": "The manual fallback for a liquidación the migration reader could not reach (gap `liquidacion_unreachable` / `liquidacion_unparseable`, whose detail names the professional, sucursal and month): Dentalink’s export (PDF, Excel, CSV or an image; `content` as base64 or a `data:` URL; at most 20 MB), stored against that professional, month and sucursal and shown in the professional’s history in place of the imported liquidación. A new upload for the same slot replaces the previous. Only a month Dentalink settled: 409 `month_owned_by_vitrina` otherwise. 400 `liquidacion_export_type` / `_encoding` / `_empty` / `_too_large`; 404 unknown professional. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic caja"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "professional_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "period": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "external_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{1,20}$"
                  },
                  "file_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "mime_type": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "professional_id",
                  "period",
                  "file_name",
                  "content"
                ],
                "additionalProperties": false
              },
              "example": {
                "professional_id": "0192f0c4-0000-7000-8000-000000000001",
                "period": "2026-09",
                "location_id": "0192f0c4-0000-7000-8000-000000000003",
                "external_id": "9000",
                "file_name": "liquidacion-septiembre.pdf",
                "content": "data:application/pdf;base64,JVBERi0xLjQK"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Liquidación export",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "0192f0c4-0000-7000-8000-000000000005",
                    "source": "dentalink_export",
                    "label": "Exportación de Dentalink",
                    "read_only": true,
                    "location_id": "0192f0c4-0000-7000-8000-000000000003",
                    "location_name": "Central",
                    "external_id": "9000",
                    "file_name": "liquidacion-septiembre.pdf",
                    "mime_type": "application/pdf",
                    "size_bytes": 48213,
                    "uploaded_at": "2026-11-06T13:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/commissions/imported-exports/{id}/file": {
      "get": {
        "summary": "Download an uploaded Dentalink liquidación export",
        "description": "The file the clinic uploaded, as uploaded (its own content type). 404 unknown id. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic caja"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/payment-registrations": {
      "get": {
        "summary": "Mercado Pago payments to register in the practice system",
        "description": "The reception tasks: each Mercado Pago payment of a clinic whose Dentalink / Medilink / Reservo authors its money, pre-filled (patient and RUT, amount, medio, MP number, date, what it paid) and, once the mirror brings the pago in, its match. `status=pending` (default) = `open` + `ambiguous` (an ambiguous task lists its `candidates`). `payments:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "matched",
                "closed",
                "all"
              ],
              "default": "pending",
              "description": "`pending` = open + ambiguous (default); `closed` = matched, dismissed or cancelled."
            },
            "required": false,
            "description": "`pending` = open + ambiguous (default); `closed` = matched, dismissed or cancelled.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The tasks, newest payment first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentRegistration"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "total",
                        "limit",
                        "offset"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199c3a0-1111-7000-8000-000000000001",
                      "status": "matched",
                      "vendor": "dentalink",
                      "vendor_label": "Dentalink",
                      "provider": "mercadopago",
                      "provider_reference": "182050716739",
                      "provider_payment_url": "https://www.mercadopago.cl/activities?q=182050716739",
                      "customer_payment_id": "0199c3a0-2222-7000-8000-000000000002",
                      "contact_id": "0199c3a0-3333-7000-8000-000000000003",
                      "amount_clp": 30000,
                      "paid_on": "2026-10-05",
                      "patient": {
                        "id": "0199c3a0-4444-7000-8000-000000000004",
                        "name": "Ana Pérez",
                        "rut": "11.111.111-1"
                      },
                      "concept": {
                        "kind": "appointment_deposit",
                        "label": "Abono cita",
                        "appointment_starts_at": "2026-10-09T13:00:00.000Z"
                      },
                      "medio": {
                        "code": "30",
                        "label": "Mercado Pago"
                      },
                      "matched": {
                        "mirror_payment_id": "0199c3a0-5555-7000-8000-000000000005",
                        "vendor_reference": "88123",
                        "received_on": "2026-10-05",
                        "amount_clp": 30000,
                        "medio": "Mercado Pago",
                        "voided": false,
                        "by": "auto_unique",
                        "at": "2026-10-05T18:20:00.000Z"
                      },
                      "candidates": [],
                      "dismissed_reason": null,
                      "overdue": false,
                      "reminded_at": null,
                      "created_at": "2026-10-05T14:02:11.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "limit": 50,
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/payment-registrations/settings": {
      "get": {
        "summary": "Which vendor medio stands for Mercado Pago",
        "description": "The practice system, its medios (the synced `medio_pago` catalog for Dentalink / Medilink, the fixed list for Reservo), the one chosen as Mercado Pago, a name-based suggestion, and after how many business days an unregistered payment reminds reception (default 2). `payments:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "responses": {
          "200": {
            "description": "The settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentRegistrationSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vendor": "dentalink",
                    "vendor_label": "Dentalink",
                    "mercadopago_medio": null,
                    "suggested_medio": "30",
                    "medios": [
                      {
                        "code": "2",
                        "label": "Tarjeta de débito",
                        "active": true
                      },
                      {
                        "code": "30",
                        "label": "Mercado Pago",
                        "active": true
                      }
                    ],
                    "reminder_business_days": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Choose the Mercado Pago medio / the reminder delay",
        "description": "Sets the vendor medio that stands for Mercado Pago (it must be one of the clinic's medios; 400 `unknown_medio`) and/or the reminder delay in business days (1–15). Re-runs the matcher: a chosen medio can settle an ambiguous task. `clinic_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mercadopago_medio": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 40,
                    "description": "The vendor medio that stands for Mercado Pago; null clears it."
                  },
                  "reminder_business_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 15
                  }
                }
              },
              "example": {
                "mercadopago_medio": "30",
                "reminder_business_days": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentRegistrationSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vendor": "dentalink",
                    "vendor_label": "Dentalink",
                    "mercadopago_medio": null,
                    "suggested_medio": "30",
                    "medios": [
                      {
                        "code": "2",
                        "label": "Tarjeta de débito",
                        "active": true
                      },
                      {
                        "code": "30",
                        "label": "Mercado Pago",
                        "active": true
                      }
                    ],
                    "reminder_business_days": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/payment-registrations/{id}": {
      "get": {
        "summary": "One Mercado Pago payment registration task",
        "description": "One task, as the list returns it. `payments:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentRegistration"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199c3a0-1111-7000-8000-000000000001",
                    "status": "matched",
                    "vendor": "dentalink",
                    "vendor_label": "Dentalink",
                    "provider": "mercadopago",
                    "provider_reference": "182050716739",
                    "provider_payment_url": "https://www.mercadopago.cl/activities?q=182050716739",
                    "customer_payment_id": "0199c3a0-2222-7000-8000-000000000002",
                    "contact_id": "0199c3a0-3333-7000-8000-000000000003",
                    "amount_clp": 30000,
                    "paid_on": "2026-10-05",
                    "patient": {
                      "id": "0199c3a0-4444-7000-8000-000000000004",
                      "name": "Ana Pérez",
                      "rut": "11.111.111-1"
                    },
                    "concept": {
                      "kind": "appointment_deposit",
                      "label": "Abono cita",
                      "appointment_starts_at": "2026-10-09T13:00:00.000Z"
                    },
                    "medio": {
                      "code": "30",
                      "label": "Mercado Pago"
                    },
                    "matched": {
                      "mirror_payment_id": "0199c3a0-5555-7000-8000-000000000005",
                      "vendor_reference": "88123",
                      "received_on": "2026-10-05",
                      "amount_clp": 30000,
                      "medio": "Mercado Pago",
                      "voided": false,
                      "by": "auto_unique",
                      "at": "2026-10-05T18:20:00.000Z"
                    },
                    "candidates": [],
                    "dismissed_reason": null,
                    "overdue": false,
                    "reminded_at": null,
                    "created_at": "2026-10-05T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/payment-registrations/{id}/confirm": {
      "post": {
        "summary": "Confirm which vendor pago records this payment",
        "description": "Staff match: the pago must be the same patient's, the same amount and inside the window (400 `not_a_candidate`), and not already record another payment (409 `candidate_taken`). 409 `registration_not_pending` once the task is closed. Closes the task and its notification. `payments:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mirror_payment_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The vendor pago (a `clinic_mirror_payment` id) that records this payment."
                  }
                },
                "required": [
                  "mirror_payment_id"
                ]
              },
              "example": {
                "mirror_payment_id": "0199c3a0-5555-7000-8000-000000000005"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The matched task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentRegistration"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199c3a0-1111-7000-8000-000000000001",
                    "status": "matched",
                    "vendor": "dentalink",
                    "vendor_label": "Dentalink",
                    "provider": "mercadopago",
                    "provider_reference": "182050716739",
                    "provider_payment_url": "https://www.mercadopago.cl/activities?q=182050716739",
                    "customer_payment_id": "0199c3a0-2222-7000-8000-000000000002",
                    "contact_id": "0199c3a0-3333-7000-8000-000000000003",
                    "amount_clp": 30000,
                    "paid_on": "2026-10-05",
                    "patient": {
                      "id": "0199c3a0-4444-7000-8000-000000000004",
                      "name": "Ana Pérez",
                      "rut": "11.111.111-1"
                    },
                    "concept": {
                      "kind": "appointment_deposit",
                      "label": "Abono cita",
                      "appointment_starts_at": "2026-10-09T13:00:00.000Z"
                    },
                    "medio": {
                      "code": "30",
                      "label": "Mercado Pago"
                    },
                    "matched": {
                      "mirror_payment_id": "0199c3a0-5555-7000-8000-000000000005",
                      "vendor_reference": "88123",
                      "received_on": "2026-10-05",
                      "amount_clp": 30000,
                      "medio": "Mercado Pago",
                      "voided": false,
                      "by": "auto_unique",
                      "at": "2026-10-05T18:20:00.000Z"
                    },
                    "candidates": [],
                    "dismissed_reason": null,
                    "overdue": false,
                    "reminded_at": null,
                    "created_at": "2026-10-05T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/payment-registrations/{id}/dismiss": {
      "post": {
        "summary": "Close a registration task without a match",
        "description": "`registered_elsewhere` (reception registered it in a way Vitrina cannot match) or `not_needed`. 409 `registration_not_pending` once the task is closed. `payments:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment registrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "enum": [
                      "registered_elsewhere",
                      "not_needed"
                    ],
                    "description": "`registered_elsewhere` = reception registered it in a way Vitrina cannot match (another amount, a split); `not_needed` = it does not belong in the practice system."
                  }
                },
                "required": [
                  "reason"
                ]
              },
              "example": {
                "reason": "registered_elsewhere"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The closed task",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PaymentRegistration"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199c3a0-1111-7000-8000-000000000001",
                    "status": "matched",
                    "vendor": "dentalink",
                    "vendor_label": "Dentalink",
                    "provider": "mercadopago",
                    "provider_reference": "182050716739",
                    "provider_payment_url": "https://www.mercadopago.cl/activities?q=182050716739",
                    "customer_payment_id": "0199c3a0-2222-7000-8000-000000000002",
                    "contact_id": "0199c3a0-3333-7000-8000-000000000003",
                    "amount_clp": 30000,
                    "paid_on": "2026-10-05",
                    "patient": {
                      "id": "0199c3a0-4444-7000-8000-000000000004",
                      "name": "Ana Pérez",
                      "rut": "11.111.111-1"
                    },
                    "concept": {
                      "kind": "appointment_deposit",
                      "label": "Abono cita",
                      "appointment_starts_at": "2026-10-09T13:00:00.000Z"
                    },
                    "medio": {
                      "code": "30",
                      "label": "Mercado Pago"
                    },
                    "matched": {
                      "mirror_payment_id": "0199c3a0-5555-7000-8000-000000000005",
                      "vendor_reference": "88123",
                      "received_on": "2026-10-05",
                      "amount_clp": 30000,
                      "medio": "Mercado Pago",
                      "voided": false,
                      "by": "auto_unique",
                      "at": "2026-10-05T18:20:00.000Z"
                    },
                    "candidates": [],
                    "dismissed_reason": null,
                    "overdue": false,
                    "reminded_at": null,
                    "created_at": "2026-10-05T14:02:11.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings": {
      "get": {
        "summary": "How this business charges, and its Mercado Pago connection",
        "description": "`charging.reservation` (refundable reservations: booking deposits, vehicle reservation deposits) and `charging.final` (balances, budgets, packs, services), each with the effective `mode`, whether it is the workspace's choice or the vertical default (`source`), and the `default_mode`. Defaults: healthcare `mp_first` for both; automotive `transfer_first` for reservations (a Mercado Pago refund does not return the commission) and `mp_first` for final payments; any other vertical `mp_first`. `provider` is which connected provider payment links go through (`preferred`: the business's choice or null; `active`: what a new link would use now or null when none is connected; `connected`: every connected provider, which the buyer chooses among on the link page when there are two or more); `webpay` is the Webpay Plus connection status and `fintoc`, `flow`, `khipu` and `bancame` the Fintoc, Flow, Khipu and banca.me («cuotas sin tarjeta») ones. `mercadopago` is the connection status plus `configured` (the environment can connect at all). `installments.min_clp`: from which amount a final-charge link offers cuotas (`null` = off). `budgets.accept_charge`: what a presupuesto's «aceptar y pagar» charges (`full` | `first_installment`). Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "Charging preference and connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "charging": {
                      "reservation": {
                        "mode": "transfer_first",
                        "source": "default",
                        "default_mode": "transfer_first"
                      },
                      "final": {
                        "mode": "mp_first",
                        "source": "tenant",
                        "default_mode": "mp_first"
                      }
                    },
                    "provider": {
                      "preferred": "webpay",
                      "active": "webpay",
                      "connected": [
                        "mercadopago",
                        "webpay",
                        "fintoc",
                        "flow",
                        "khipu",
                        "bancame"
                      ]
                    },
                    "webpay": {
                      "connected": true,
                      "environment": "integration",
                      "commerce_code": "597055555532",
                      "api_key_preview": "579B••••1C",
                      "connected_at": "2026-10-20T14:00:00.000Z"
                    },
                    "fintoc": {
                      "connected": true,
                      "mode": "test",
                      "secret_key_preview": "••••Ab12",
                      "connected_at": "2026-10-20T14:00:00.000Z"
                    },
                    "flow": {
                      "connected": true,
                      "environment": "sandbox",
                      "api_key": "flow-api-key-example",
                      "secret_key_preview": "4c1a••••9e",
                      "connected_at": "2026-10-20T14:00:00.000Z"
                    },
                    "khipu": {
                      "connected": true,
                      "api_key_preview": "9d2f••••a7",
                      "connected_at": "2026-10-20T14:00:00.000Z"
                    },
                    "bancame": {
                      "connected": true,
                      "environment": "automation",
                      "merchant_entity": "ex-46df3310759e9000",
                      "public_key": "pk_bancame_example",
                      "private_key_preview": "eyJh••••c5",
                      "connected_at": "2026-11-02T14:00:00.000Z"
                    },
                    "installments": {
                      "min_clp": null
                    },
                    "budgets": {
                      "accept_charge": "full"
                    },
                    "mercadopago": {
                      "connected": true,
                      "mp_user_id": "1234567890",
                      "mp_nickname": "AUTOMOTORAEJEMPLO",
                      "environment": "production",
                      "connected_at": "2026-10-08T14:00:00.000Z",
                      "last_sync_at": "2026-10-08T15:00:00.000Z",
                      "last_sync_status": "ok",
                      "last_sync_error": null,
                      "configured": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/charging": {
      "put": {
        "summary": "Set how this business charges, per charge type",
        "description": "Pass `reservation` and/or `final`; `null` resets that type to the vertical default, an omitted type is left as it is. Every payment block (the agent booking tools, the hosted booking page) follows it. Returns the resolved preference. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reservation": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "mp_first",
                      "transfer_first",
                      "mp_only",
                      "transfer_only"
                    ],
                    "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                  },
                  "final": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "mp_first",
                      "transfer_first",
                      "mp_only",
                      "transfer_only"
                    ],
                    "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                  }
                }
              },
              "example": {
                "reservation": "transfer_first",
                "final": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resolved charging preference",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "reservation": {
                      "mode": "transfer_first",
                      "source": "default",
                      "default_mode": "transfer_first"
                    },
                    "final": {
                      "mode": "mp_first",
                      "source": "tenant",
                      "default_mode": "mp_first"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/installments": {
      "put": {
        "summary": "Set from which amount a payment link offers installments",
        "description": "From `min_clp` CLP up, a Mercado Pago link for a FINAL charge (balance, budget, pack) offers installments («cuotas»); `null` turns them off (the default). Reservations and deposits are always one payment. Returns the saved threshold. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "min_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0,
                    "description": "Whole CLP from which a final charge (balance, budget, pack) may be paid in installments. `null` turns installments off. Reservations and deposits are always one payment.",
                    "example": 200000
                  }
                },
                "required": [
                  "min_clp"
                ],
                "additionalProperties": false
              },
              "example": {
                "min_clp": 200000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved threshold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "min_clp": 200000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/budgets": {
      "put": {
        "summary": "Set what «aceptar y pagar» charges for a presupuesto",
        "description": "When the charging preference offers a Mercado Pago link for final charges, the public presupuesto page offers «aceptar y pagar». `accept_charge` decides its amount: `full` (default) or `first_installment` (the accepted amount divided by the presupuesto's `installments`). Returns the saved choice. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accept_charge": {
                    "type": "string",
                    "enum": [
                      "full",
                      "first_installment"
                    ],
                    "description": "What the patient pays when they accept a presupuesto from its link: `full` (the whole amount being accepted, the default) or `first_installment` (that amount divided by the presupuesto's `installments`; the whole amount when it has none).",
                    "example": "first_installment"
                  }
                },
                "required": [
                  "accept_charge"
                ],
                "additionalProperties": false
              },
              "example": {
                "accept_charge": "first_installment"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved choice",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "accept_charge": "first_installment"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/mercadopago/status": {
      "get": {
        "summary": "Mercado Pago connection status",
        "description": "Connected as … / last sync / last sync error, or `connected: false`. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "mp_user_id": "1234567890",
                    "mp_nickname": "AUTOMOTORAEJEMPLO",
                    "environment": "production",
                    "connected_at": "2026-10-08T14:00:00.000Z",
                    "last_sync_at": "2026-10-08T15:00:00.000Z",
                    "last_sync_status": "ok",
                    "last_sync_error": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/mercadopago/connect": {
      "post": {
        "summary": "Start the Mercado Pago OAuth consent flow",
        "description": "Returns the consent URL for a person to open. Connects the business's OWN Mercado Pago account, never Vitrina's. `return_to` is where the browser lands after the callback. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uri"
            },
            "required": false,
            "name": "return_to",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The consent URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://auth.mercadopago.cl/authorization?client_id=4688094843266502&response_type=code&platform_id=mp&state=…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/mercadopago/disconnect": {
      "post": {
        "summary": "Disconnect the Mercado Pago account",
        "description": "Reversible: the connection survives disabled and a later reconnect re-enables it. Payment blocks fall back to the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/mercadopago/sync": {
      "post": {
        "summary": "Sincronizar ahora — enqueue an incremental pull",
        "description": "Enqueues the same job the daily tick uses; dedups with an in-flight sync. 400 when nothing is connected. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "202": {
            "description": "Enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enqueued": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/provider": {
      "put": {
        "summary": "Choose which connected provider payment links use",
        "description": "When the business connected more than one customer payment provider (Mercado Pago, Webpay Plus, Fintoc, Flow, Khipu, banca.me), payment links go through the one chosen here; `null` clears the choice (the first connected one is used). A choice that is not connected is ignored at link time. Returns the choice and the provider a new link would use now. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "provider": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "mercadopago",
                      "webpay",
                      "fintoc",
                      "flow",
                      "khipu",
                      "bancame"
                    ],
                    "description": "The provider payment links go through when the business connected more than one. `null` = the first connected one. A provider that is not connected is ignored at link time.",
                    "example": "webpay"
                  }
                },
                "required": [
                  "provider"
                ],
                "additionalProperties": false
              },
              "example": {
                "provider": "webpay"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The choice and the active provider",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "preferred": "webpay",
                    "active": "webpay"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/webpay/status": {
      "get": {
        "summary": "Webpay Plus connection status",
        "description": "Connected commerce code, environment and a masked key preview, or `connected: false`. The secret is never returned. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "integration",
                    "commerce_code": "597055555532",
                    "api_key_preview": "579B••••1C",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/webpay/connect": {
      "post": {
        "summary": "Connect the business's own Webpay Plus commerce",
        "description": "Connects the business's OWN Transbank commerce (code + API key secret); the money never passes through Vitrina. The pair is verified against Transbank for the chosen `environment` before it is stored encrypted: 400 when Transbank rejects it. Use `integration` with Transbank's public test commerce to try it with test cards. A reconnect replaces the credentials. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commerce_code": {
                    "type": "string",
                    "pattern": "^[0-9]{6,20}$",
                    "description": "The commerce code («código de comercio») Transbank issued, digits only.",
                    "example": "597055555532"
                  },
                  "api_key_secret": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 200,
                    "description": "The commerce API key secret («llave secreta»). Verified against Transbank, stored encrypted, never returned."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "integration",
                      "production"
                    ],
                    "description": "`integration` = Transbank sandbox (test cards, no real money); `production` = real charges.",
                    "example": "integration"
                  }
                },
                "required": [
                  "commerce_code",
                  "api_key_secret",
                  "environment"
                ],
                "additionalProperties": false
              },
              "example": {
                "commerce_code": "597055555532",
                "api_key_secret": "<your commerce API key secret>",
                "environment": "integration"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "integration",
                    "commerce_code": "597055555532",
                    "api_key_preview": "579B••••1C",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/webpay/disconnect": {
      "post": {
        "summary": "Disconnect Webpay Plus",
        "description": "Reversible: the connection survives disabled and a later connect re-enables it. Payment links fall back to another connected provider or the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/fintoc/status": {
      "get": {
        "summary": "Fintoc connection status",
        "description": "Connected mode (`test` | `live`) and a masked key preview, or `connected: false`. The secret is never returned. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "mode": "test",
                    "secret_key_preview": "••••Ab12",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/fintoc/connect": {
      "post": {
        "summary": "Connect the business's own Fintoc account",
        "description": "Connects the business's OWN Fintoc account with its secret key; the money never passes through Vitrina. The key is verified against Fintoc (400 when Fintoc rejects it), a webhook endpoint pointing at Vitrina is registered on that account, and the key and the endpoint secret are stored encrypted. A test-mode key is Fintoc’s sandbox (test banks, no real money); a live-mode key charges real transfers. A reconnect replaces the credentials and the endpoint. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "secret_key": {
                    "type": "string",
                    "maxLength": 200,
                    "pattern": "^sk_(test|live)_[A-Za-z0-9_-]{8,}$",
                    "description": "The Fintoc secret key from the business’s dashboard (a test-mode key is the sandbox, a live-mode key moves real money). Verified against Fintoc, stored encrypted, never returned."
                  }
                },
                "required": [
                  "secret_key"
                ],
                "additionalProperties": false
              },
              "example": {
                "secret_key": "<your Fintoc secret key>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "mode": "test",
                    "secret_key_preview": "••••Ab12",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/fintoc/disconnect": {
      "post": {
        "summary": "Disconnect Fintoc",
        "description": "Reversible: removes the webhook endpoint Vitrina registered and disables the connection; a later connect re-enables it. Payment links fall back to another connected provider or the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/flow/status": {
      "get": {
        "summary": "Flow connection status",
        "description": "Connected API key, environment and a masked secret key preview, or `connected: false`. The secret is never returned. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "sandbox",
                    "api_key": "flow-api-key-example",
                    "secret_key_preview": "4c1a••••9e",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/flow/connect": {
      "post": {
        "summary": "Connect the business's own Flow commerce",
        "description": "Connects the business's OWN Flow commerce (API Key + Secret Key); the money never passes through Vitrina. The pair is verified against Flow for the chosen `environment` before it is stored encrypted: 400 when Flow rejects it. Use `sandbox` (sandbox.flow.cl) to try it without real money. A reconnect replaces the credentials. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "api_key": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9-]{8,80}$",
                    "description": "The commerce API Key («ApiKey») from the Flow panel. Not a secret.",
                    "example": "flow-api-key-example"
                  },
                  "secret_key": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 200,
                    "description": "The commerce Secret Key («SecretKey»). Verified against Flow, stored encrypted, never returned."
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "sandbox",
                      "production"
                    ],
                    "description": "`sandbox` = sandbox.flow.cl (test payments, no real money); `production` = real charges.",
                    "example": "sandbox"
                  }
                },
                "required": [
                  "api_key",
                  "secret_key",
                  "environment"
                ],
                "additionalProperties": false
              },
              "example": {
                "api_key": "flow-api-key-example",
                "secret_key": "<your commerce Secret Key>",
                "environment": "sandbox"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "sandbox",
                    "api_key": "flow-api-key-example",
                    "secret_key_preview": "4c1a••••9e",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/flow/disconnect": {
      "post": {
        "summary": "Disconnect Flow",
        "description": "Reversible: the connection survives disabled and a later connect re-enables it. Payment links fall back to another connected provider or the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/khipu/status": {
      "get": {
        "summary": "Khipu connection status",
        "description": "Whether a Khipu collection account is connected, with a masked key preview. The secret is never returned. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "api_key_preview": "9d2f••••a7",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/khipu/connect": {
      "post": {
        "summary": "Connect the business's own Khipu account",
        "description": "Connects the business's OWN Khipu collection account with its secret key; the money never passes through Vitrina. The key is verified against Khipu before it is stored encrypted: 400 when Khipu rejects it. Khipu has no separate sandbox: a developer collection account accepts only Khipu's DemoBank test bank, so connect one to try it without real money. A reconnect replaces the key. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "api_key": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 200,
                    "description": "The collection account secret key. It authenticates API calls and signs Khipu notifications. Verified against Khipu, stored encrypted, never returned. A developer collection account accepts only the DemoBank test bank."
                  }
                },
                "required": [
                  "api_key"
                ],
                "additionalProperties": false
              },
              "example": {
                "api_key": "<your collection account secret key>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "api_key_preview": "9d2f••••a7",
                    "connected_at": "2026-10-20T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/khipu/disconnect": {
      "post": {
        "summary": "Disconnect Khipu",
        "description": "Reversible: the connection survives disabled and a later connect re-enables it. Payment links fall back to another connected provider or the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/bancame/status": {
      "get": {
        "summary": "banca.me connection status",
        "description": "Whether a banca.me partner account («cuotas sin tarjeta») is connected: its `environment`, merchant `merchant_entity` (banca.me’s `entityId`), `public_key` (not a secret) and a masked `private_key_preview`. The private key and the webhook secret are never returned. Requires `tenant_bank_accounts:read` (or `clinic_money:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "automation",
                    "merchant_entity": "ex-46df3310759e9000",
                    "public_key": "pk_bancame_example",
                    "private_key_preview": "eyJh••••c5",
                    "connected_at": "2026-11-02T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-settings/bancame/connect": {
      "post": {
        "summary": "Connect the business's own banca.me account",
        "description": "Connects the business's OWN banca.me partner account (buy now, pay later: the buyer pays in installments from a bank account, no card; banca.me pays the business). The money never passes through Vitrina. The private key is verified against banca.me (400 `bancame_key_rejected` when banca.me refuses it), then the merchant (400 `bancame_entity_rejected` for an unknown or inactive `merchant_entity` or one without BNPL), then Vitrina registers its `loan_approved` webhook on the account (400 `bancame_webhook_limit` when the account already has 10 active webhooks) and stores the private key and the webhook secret encrypted; nothing is stored when a step fails. Use `automation` to try it with banca.me’s fixed test outcomes. A reconnect replaces the credentials and the webhook. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "private_key": {
                    "type": "string",
                    "minLength": 16,
                    "maxLength": 4000,
                    "description": "The partner Private Key («llave privada», the API token banca.me shares through 1Password) for the chosen environment. Verified against banca.me, stored encrypted, never returned."
                  },
                  "public_key": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 2000,
                    "description": "The partner Public Key: it opens banca.me’s payment page. Not a secret; returned by the status."
                  },
                  "merchant_entity": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_.:-]{1,120}$",
                    "description": "The merchant id («entityId») banca.me registered for this business. Verified against banca.me.",
                    "example": "ex-46df3310759e9000"
                  },
                  "environment": {
                    "type": "string",
                    "enum": [
                      "automation",
                      "beta",
                      "production"
                    ],
                    "description": "`automation` = banca.me’s test environment with fixed outcomes per RUT (33333333-3 approves, 11111111-1 is rejected); `beta` = development copy of production; `production` = real loans.",
                    "example": "automation"
                  }
                },
                "required": [
                  "private_key",
                  "public_key",
                  "merchant_entity",
                  "environment"
                ],
                "additionalProperties": false
              },
              "example": {
                "private_key": "<your partner Private Key>",
                "public_key": "<your partner Public Key>",
                "merchant_entity": "ex-46df3310759e9000",
                "environment": "automation"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "automation",
                    "merchant_entity": "ex-46df3310759e9000",
                    "public_key": "pk_bancame_example",
                    "private_key_preview": "eyJh••••c5",
                    "connected_at": "2026-11-02T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payment-settings/bancame/disconnect": {
      "post": {
        "summary": "Disconnect banca.me",
        "description": "Reversible: the connection survives disabled and a later connect re-enables it (with a new webhook). Vitrina deactivates its webhook on the account. Payment links fall back to another connected provider or the transfer. Requires `tenant_bank_accounts:write` (or `clinic_money:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payment settings"
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/bank-feed/sources": {
      "get": {
        "summary": "Bank feed sources available to this clinic",
        "description": "The e-mail rail (free, always on), Fintoc (optional add-on `bank_feed_fintoc`, billed per connected bank per month at the provider's list price — `price_clp_per_bank_month` is null until it is set) and BCI API Market (not configured until the partner approval lands). Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "responses": {
          "200": {
            "description": "The sources",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankFeedSources"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "email": {
                      "available": true,
                      "billed": false
                    },
                    "fintoc": {
                      "configured": true,
                      "entitled": true,
                      "feature": "bank_feed_fintoc",
                      "price_clp_per_bank_month": null,
                      "connected_banks": 1
                    },
                    "bci_api_market": {
                      "configured": false,
                      "reason": "BCI API Market production access is pending partner approval."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/bank-feed/connections": {
      "get": {
        "summary": "Connected bank links",
        "description": "Every bank link this clinic connected through an API source, enabled or not, with masked account numbers and the last sync result. Requires `clinic_money:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "responses": {
          "200": {
            "description": "The connections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BankFeedConnection"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "bf0bf0bf-0000-4000-8000-000000000001",
                      "provider": "fintoc",
                      "institution_name": "Banco de Chile",
                      "holder_name": "Clínica Ejemplo SpA",
                      "accounts": [
                        {
                          "id": "acc_9Xz2Lm",
                          "number_masked": "••••4821"
                        }
                      ],
                      "enabled": true,
                      "connected_at": "2026-09-20T14:02:11.000Z",
                      "disconnected_at": null,
                      "last_sync_at": "2026-09-22T09:00:04.000Z",
                      "last_sync_status": "ok",
                      "last_sync_error": null,
                      "sync_cursor": "2026-09-22"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/bank-feed/fintoc/connect": {
      "post": {
        "summary": "Connect a bank through Fintoc",
        "description": "Send the `exchange_token` Fintoc's widget returns (or a `link_token`). The server swaps it for the long-lived link token, stores it encrypted, and queues the first pull. Requires the `bank_feed_fintoc` add-on — without it the answer is `402 ENTITLEMENT_NOT_ACTIVE` (the upsell). Reconnecting the same bank updates its connection. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankFeedFintocConnectBody"
              },
              "example": {
                "exchange_token": "exch_7f3a9b2c1d"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankFeedConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "bf0bf0bf-0000-4000-8000-000000000001",
                    "provider": "fintoc",
                    "institution_name": "Banco de Chile",
                    "holder_name": "Clínica Ejemplo SpA",
                    "accounts": [
                      {
                        "id": "acc_9Xz2Lm",
                        "number_masked": "••••4821"
                      }
                    ],
                    "enabled": true,
                    "connected_at": "2026-09-20T14:02:11.000Z",
                    "disconnected_at": null,
                    "last_sync_at": "2026-09-22T09:00:04.000Z",
                    "last_sync_status": "ok",
                    "last_sync_error": null,
                    "sync_cursor": "2026-09-22"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/bank-feed/connections/{id}/sync": {
      "post": {
        "summary": "Sincronizar ahora — enqueue a pull of one bank link",
        "description": "Enqueues the SAME job the daily tick uses; the stable per-connection jobId dedups with an in-flight sync. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enqueued": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "enqueued"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enqueued": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/bank-feed/connections/{id}/disconnect": {
      "post": {
        "summary": "Disconnect a bank link",
        "description": "Reversible: the connection survives disabled (its movements stay in the ledger), stops syncing and stops counting as a billed bank from the next period. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/bank-feed/email/pull": {
      "post": {
        "summary": "Re-read bank e-mails still in the inbox for a window",
        "description": "Runs the e-mail source of the bank feed over `[from, to]` (Santiago days, ≤ 92 days): bank comprobantes that are still conversations in a connected mailbox become `bank_movement` rows (provenance `backfill` — historical, never announced to anyone). Idempotent. Requires `clinic_money:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic bank feed"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BankFeedEmailPullBody"
              },
              "example": {
                "from": "2026-09-01",
                "to": "2026-09-22"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was ingested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BankFeedIngestResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "drafts": 0,
                    "inserted": 3,
                    "deduped": 1,
                    "cross_source_twins": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/accounts": {
      "get": {
        "summary": "Plan de cuentas: the account tree",
        "description": "Every account of the workspace as a flat list in tree order (parents before children, siblings in screen order), each with `depth` and `path`. The first read of a workspace that holds the add-on creates the starter chart for its vertical (clinic, automotora or generic). Archived accounts are left out unless `include_archived=true`. With `usage_month=YYYY-MM` every account gains `usage` (that month’s net total, document count, last month used and whether it was used in the last 6 months), the same arithmetic as the Resultado; parents roll up their children. `usage_month` must be a month of the workspace (not after the current one) or the answer is `422 FINANZAS_ACCOUNT_INVALID` with `details.reason = \"usage_month\"`, and it also requires `finanzas_reports:read` (`403` with `details.reason = \"usage_requires_reports\"` otherwise). The plan de cuentas is a classification tree, not double-entry. Each account has a `kind` (`ingreso`, `costo`, `gasto` or `no_operacional`) and, on `costo` and `gasto` only, a `behavior` (`fijo` or `variable`). A sub-account has its parent’s kind. Rule breaks answer `422 FINANZAS_ACCOUNT_INVALID` and state conflicts `409 FINANZAS_ACCOUNT_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_archived",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": false,
            "name": "usage_month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccounts"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "accounts": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                        "parent_id": null,
                        "name": "Gastos de administración",
                        "description": null,
                        "kind": "gasto",
                        "behavior": "fijo",
                        "no_document": false,
                        "position": 2,
                        "depth": 1,
                        "path": "Gastos de administración",
                        "seeded": true,
                        "has_children": true,
                        "archived_at": null,
                        "created_at": "2026-10-01T14:03:00.000Z",
                        "updated_at": "2026-10-01T14:03:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                        "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                        "name": "Arriendo de oficina",
                        "description": "Arriendo mensual de la oficina central.",
                        "kind": "gasto",
                        "behavior": "fijo",
                        "no_document": false,
                        "position": 0,
                        "depth": 2,
                        "path": "Gastos de administración > Arriendo de oficina",
                        "seeded": false,
                        "has_children": false,
                        "archived_at": null,
                        "created_at": "2026-10-30T15:20:00.000Z",
                        "updated_at": "2026-10-30T15:20:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add an account",
        "description": "Adds an account at the end of its group. A top-level account needs `kind`; a sub-account takes its parent’s. `behavior` is required on `costo` and `gasto` (a sub-account inherits its parent’s when omitted) and refused elsewhere. Names are unique among live siblings, case-insensitive. At most 5 levels. The plan de cuentas is a classification tree, not double-entry. Each account has a `kind` (`ingreso`, `costo`, `gasto` or `no_operacional`) and, on `costo` and `gasto` only, a `behavior` (`fijo` or `variable`). A sub-account has its parent’s kind. Rule breaks answer `422 FINANZAS_ACCOUNT_INVALID` and state conflicts `409 FINANZAS_ACCOUNT_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasAccountCreateBody"
              },
              "example": {
                "name": "Arriendo de oficina",
                "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                "description": "Arriendo mensual de la oficina central."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                    "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "name": "Arriendo de oficina",
                    "description": "Arriendo mensual de la oficina central.",
                    "kind": "gasto",
                    "behavior": "fijo",
                    "no_document": false,
                    "position": 0,
                    "depth": 2,
                    "path": "Gastos de administración > Arriendo de oficina",
                    "seeded": false,
                    "has_children": false,
                    "archived_at": null,
                    "created_at": "2026-10-30T15:20:00.000Z",
                    "updated_at": "2026-10-30T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/accounts/review": {
      "get": {
        "summary": "Plan de cuentas: the monthly review",
        "description": "Is the plan in order? How much of the month’s outflow has an account (`classified_pct`), the documents nobody classified (the Resultado’s «Egresos sin clasificar»), and up to three findings in priority order: `unclassified`, `duplicate_import` (accounts an import brought that repeat a template account) and `unused` (leaf accounts with no money in 6 months). Default `month`: the latest closed month, else the previous month, else the current one (`in_progress`). A `month` outside the workspace answers `422 FINANZAS_ACCOUNT_INVALID` (`details.reason = \"month\"`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read` and `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": false,
            "name": "month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The review",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccountReview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "in_progress": false,
                    "classified_pct": 95.7,
                    "outflow_total_clp": 37000000,
                    "unclassified": {
                      "count": 7,
                      "total_clp": 1590984,
                      "by_kind": [
                        {
                          "kind": "purchase",
                          "count": 7,
                          "total_clp": 1590984
                        }
                      ],
                      "with_proposal": 4
                    },
                    "unclassified_income": {
                      "count": 0,
                      "total_clp": 0
                    },
                    "findings": [
                      {
                        "key": "unclassified",
                        "count": 7,
                        "total_clp": 1590984
                      },
                      {
                        "key": "duplicate_import",
                        "count": 1,
                        "pairs": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d03",
                            "name": "Ret. Boletas de Honorarios (auto)",
                            "duplicates_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d04",
                            "duplicates_name": "Retención de boletas de honorarios",
                            "used_6m": true
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/accounts/review/dismiss": {
      "post": {
        "summary": "Plan de cuentas: «No son la misma»",
        "description": "Stops a `duplicate_import` pair from appearing in the review again. Idempotent. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasAccountReviewDismissBody"
              },
              "example": {
                "key": "duplicate_import",
                "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d03"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "dismissed": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "dismissed"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dismissed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/accounts/{id}": {
      "delete": {
        "summary": "Delete an unused account",
        "description": "Only an account nothing was classified under can be deleted. With sub-accounts it answers `409 has_children`; with classified documents or movements `409 has_history`: archive it instead. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Rename, move or retag an account",
        "description": "Change `name`, `description`, `behavior` (costo and gasto only), `no_document` («No lleva documento»: the account opens no «Falta documento» case and its money out is not counted in «Egresos sin documento»), `position` (order among siblings) or move the account with `parent_id` (`null` = top level). A move keeps the account’s id, so every classification follows it; it is refused under itself or its sub-accounts (`cycle`), under another kind (`kind_mismatch`) or beyond 5 levels (`too_deep`). The kind itself never changes. An archived account cannot be edited. The plan de cuentas is a classification tree, not double-entry. Each account has a `kind` (`ingreso`, `costo`, `gasto` or `no_operacional`) and, on `costo` and `gasto` only, a `behavior` (`fijo` or `variable`). A sub-account has its parent’s kind. Rule breaks answer `422 FINANZAS_ACCOUNT_INVALID` and state conflicts `409 FINANZAS_ACCOUNT_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasAccountUpdateBody"
              },
              "example": {
                "name": "Arriendo de oficina central",
                "position": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                    "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "name": "Arriendo de oficina central",
                    "description": "Arriendo mensual de la oficina central.",
                    "kind": "gasto",
                    "behavior": "fijo",
                    "no_document": false,
                    "position": 1,
                    "depth": 2,
                    "path": "Gastos de administración > Arriendo de oficina",
                    "seeded": false,
                    "has_children": false,
                    "archived_at": null,
                    "created_at": "2026-10-30T15:20:00.000Z",
                    "updated_at": "2026-10-30T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/accounts/{id}/archive": {
      "post": {
        "summary": "Archive an account",
        "description": "Hides the account from pickers; it stays in the data and in reports. Refused while it has live sub-accounts (`has_active_children`). Idempotent. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The archived account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                    "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "name": "Arriendo de oficina",
                    "description": "Arriendo mensual de la oficina central.",
                    "kind": "gasto",
                    "behavior": "fijo",
                    "no_document": false,
                    "position": 0,
                    "depth": 2,
                    "path": "Gastos de administración > Arriendo de oficina",
                    "seeded": false,
                    "has_children": false,
                    "archived_at": "2026-10-30T16:00:00.000Z",
                    "created_at": "2026-10-30T15:20:00.000Z",
                    "updated_at": "2026-10-30T16:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/accounts/{id}/restore": {
      "post": {
        "summary": "Restore an archived account",
        "description": "Brings an archived account back. Its parent must be live (`parent_archived`). Idempotent. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The restored account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                    "parent_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "name": "Arriendo de oficina",
                    "description": "Arriendo mensual de la oficina central.",
                    "kind": "gasto",
                    "behavior": "fijo",
                    "no_document": false,
                    "position": 0,
                    "depth": 2,
                    "path": "Gastos de administración > Arriendo de oficina",
                    "seeded": false,
                    "has_children": false,
                    "archived_at": null,
                    "created_at": "2026-10-30T15:20:00.000Z",
                    "updated_at": "2026-10-30T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connections": {
      "get": {
        "summary": "Money connections: banks, Mercado Pago and SII",
        "description": "Every money connection of the workspace (Ajustes › Bancos y SII): bank links, the Mercado Pago account and the SII connection per company, each with its accounts (type and currency), movement count, last sync, last successful sync and a status: `ok`, `pending`, `stale` (no successful sync for more than 48 h), `error`, `login_required` (the bank login must be redone: reconnect) or `disconnected`. `next_refresh_at` says when a manual sync is allowed again. `fintoc.widget_public_key` is the publishable key to open the bank-link widget with (null until configured). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The connections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasConnections"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connections": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                        "kind": "bank",
                        "provider": "fintoc",
                        "institution_name": "Banco de Chile",
                        "holder_name": "Clínica Ejemplo SpA",
                        "accounts": [
                          {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                            "number_masked": "****4821",
                            "type": "cuenta_corriente",
                            "currency": "CLP"
                          }
                        ],
                        "movement_count": 1284,
                        "connected_at": "2026-10-01T14:03:00.000Z",
                        "last_sync_at": "2026-10-30T08:00:00.000Z",
                        "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                        "last_sync_error": null,
                        "status": "ok",
                        "next_refresh_at": "2026-10-30T09:00:00.000Z",
                        "actions": {
                          "sync_now": true,
                          "reconnect": false,
                          "disconnect": true
                        }
                      }
                    ],
                    "fintoc": {
                      "configured": true,
                      "entitled": true,
                      "feature": "bank_feed_fintoc",
                      "price_clp_per_bank_month": 15000,
                      "widget_public_key": "pk_test_ejemplo"
                    },
                    "stale_after_hours": 48,
                    "manual_sync_min_interval_minutes": 60
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connections/fintoc/link-intent": {
      "post": {
        "summary": "Start a bank link (Fintoc widget token)",
        "description": "Opens one bank-link attempt: creates a Fintoc link intent (business holder, movements) and returns its `widget_token`, which opens Fintoc's widget (`Fintoc.create({ publicKey, widgetToken })`). Fintoc returns the token only once, so request a fresh one each time the widget opens. On success the widget hands back an `exchange_token`: send it to `POST /finanzas/connections/fintoc` (or `…/{id}/reconnect`). Reads no bank and costs nothing. Requires the `bank_feed_fintoc` add-on (`402 ENTITLEMENT_NOT_ACTIVE` otherwise). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "201": {
            "description": "The link intent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasFintocLinkIntent"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "widget_token": "widget_token_ejemplo_123",
                    "external_id": "li_ejemplo123"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/connections/fintoc": {
      "post": {
        "summary": "Connect a bank account",
        "description": "Send the `exchange_token` Fintoc's widget returns (or a `link_token`). The link token is stored encrypted and the first sync is queued. Each bank is billed at Fintoc's cost through the `bank_feed_fintoc` add-on; without it the answer is `402 ENTITLEMENT_NOT_ACTIVE` (`required: [\"bank_feed_fintoc\"]`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasFintocConnectBody"
              },
              "example": {
                "exchange_token": "exchange_token_ejemplo_123"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "kind": "bank",
                    "provider": "fintoc",
                    "institution_name": "Banco de Chile",
                    "holder_name": "Clínica Ejemplo SpA",
                    "accounts": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "number_masked": "****4821",
                        "type": "cuenta_corriente",
                        "currency": "CLP"
                      }
                    ],
                    "movement_count": 1284,
                    "connected_at": "2026-10-01T14:03:00.000Z",
                    "last_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_sync_error": null,
                    "status": "ok",
                    "next_refresh_at": "2026-10-30T09:00:00.000Z",
                    "actions": {
                      "sync_now": true,
                      "reconnect": false,
                      "disconnect": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/connections/sync": {
      "get": {
        "summary": "Freshness of the bank movements",
        "description": "When each bank link was last pulled, and whether a pull is queued. A read: it queues nothing. Poll it after `POST /finanzas/connections/sync` until `queued` is false, then reload. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The freshness",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankPull"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "last_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                    "queued": true,
                    "enqueued": 0,
                    "links": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                        "institution_name": "Banco de Chile",
                        "last_sync_at": "2026-10-30T08:00:00.000Z",
                        "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                        "queued": true,
                        "next_refresh_at": "2026-10-30T09:15:00.000Z",
                        "skipped": null
                      }
                    ],
                    "min_interval_minutes": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Sync every bank link",
        "description": "Sync on entry to Finanzas and «Sincronizar ahora» for every bank link. Idempotent, never throttled: each active link not pulled nor requested in the last `min_interval_minutes` (15) gets one pull, the others answer with their last pull (`enqueued` counts the pulls this call queued). A `login_required` or disconnected link is skipped and reported in `links[].skipped`. Movements land within a minute or two; poll `GET /finanzas/connections/sync` until `queued` is false. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The freshness after the request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankPull"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "last_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                    "queued": true,
                    "enqueued": 1,
                    "links": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                        "institution_name": "Banco de Chile",
                        "last_sync_at": "2026-10-30T08:00:00.000Z",
                        "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                        "queued": true,
                        "next_refresh_at": "2026-10-30T09:15:00.000Z",
                        "skipped": null
                      }
                    ],
                    "min_interval_minutes": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/connections/{id}/sync": {
      "post": {
        "summary": "Sync a connection now",
        "description": "«Sincronizar ahora». Queues a pull of a bank link or the Mercado Pago account. A bank link can be synced on demand at most once per `manual_sync_min_interval_minutes`; sooner answers `429 FINANZAS_SYNC_THROTTLED` with `details.next_refresh_at`. A `login_required` link, a disconnected one or an SII connection answers 409. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasSyncResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enqueued": true,
                    "next_refresh_at": "2026-10-30T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connections/{id}/reconnect": {
      "post": {
        "summary": "Reconnect a bank account",
        "description": "For a bank link whose login must be redone (`login_required`). Send the `exchange_token` from Fintoc's widget opened again for that bank. The link returns to `active`; if Fintoc answers with a different link, it replaces this one, which is disconnected. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasFintocConnectBody"
              },
              "example": {
                "exchange_token": "exchange_token_ejemplo_123"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "kind": "bank",
                    "provider": "fintoc",
                    "institution_name": "Banco de Chile",
                    "holder_name": "Clínica Ejemplo SpA",
                    "accounts": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "number_masked": "****4821",
                        "type": "cuenta_corriente",
                        "currency": "CLP"
                      }
                    ],
                    "movement_count": 1284,
                    "connected_at": "2026-10-01T14:03:00.000Z",
                    "last_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_successful_sync_at": "2026-10-30T08:00:00.000Z",
                    "last_sync_error": null,
                    "status": "ok",
                    "next_refresh_at": "2026-10-30T09:00:00.000Z",
                    "actions": {
                      "sync_now": true,
                      "reconnect": false,
                      "disconnect": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/connections/{id}/disconnect": {
      "post": {
        "summary": "Disconnect a bank account",
        "description": "Reversible: the link stays (disabled), its movements stay in the ledger, it stops syncing and stops counting as a billed bank. Bank links only. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/howto-videos": {
      "get": {
        "summary": "«Cómo se usa» videos for the Finanzas screens",
        "description": "The Finanzas screens that have a how-to video, in nav order, each with the link and the `embed_url` to play in a dialog. A screen with no video is absent (show no «Cómo se usa» button there). The list is managed by Vitrina and is the same for every workspace. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The videos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasHowtoVideos"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "videos": [
                      {
                        "screen": "resumen",
                        "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
                        "embed_url": "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ops/finanzas-howto-videos": {
      "get": {
        "summary": "Manage «Cómo se usa» videos: list every screen",
        "description": "Every Finanzas screen with its label and its video link (`null` when none), plus the accepted video hosts. Vitrina's own management, not a workspace's: an operator key holding `billing_ops:admin` or a Vitrina staff session on the founders' console. Any other caller gets 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "Every screen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasHowtoAdminView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "screens": [
                      {
                        "screen": "resumen",
                        "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
                        "embed_url": "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ",
                        "label": "Resumen"
                      },
                      {
                        "screen": "por_resolver",
                        "label": "Por resolver",
                        "url": null,
                        "embed_url": null
                      }
                    ],
                    "allowed_hosts": [
                      "youtube.com",
                      "youtu.be",
                      "vimeo.com",
                      "loom.com",
                      "drive.google.com"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ops/finanzas-howto-videos/{screen}": {
      "put": {
        "summary": "Set or change a screen's «Cómo se usa» video",
        "description": "Sets the video link of one Finanzas screen for every workspace, replacing any previous one. Only https video links from YouTube (watch, youtu.be, shorts, embed), Vimeo, Loom (share/embed) or Google Drive (file/d/…) are accepted; anything else answers `400 VALIDATION_ERROR`. Vitrina's own management, not a workspace's: an operator key holding `billing_ops:admin` or a Vitrina staff session on the founders' console. Any other caller gets 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/FinanzasHowtoScreen"
            },
            "required": true,
            "name": "screen",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasHowtoSetBody"
              },
              "example": {
                "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The screen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasHowtoAdminRow"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "screen": "resumen",
                    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
                    "embed_url": "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ",
                    "label": "Resumen"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Clear a screen's «Cómo se usa» video",
        "description": "Removes the video of one Finanzas screen; workspaces stop showing its button. Idempotent. Vitrina's own management, not a workspace's: an operator key holding `billing_ops:admin` or a Vitrina staff session on the founders' console. Any other caller gets 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/FinanzasHowtoScreen"
            },
            "required": true,
            "name": "screen",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Cleared"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connections/{id}/clave-tributaria": {
      "get": {
        "summary": "SII clave status for boletas de honorarios",
        "description": "The state of the Clave Tributaria an SII connection uses to read boletas de honorarios: `activa` (with the last verified date), `verificando`, `requiere_actualizar` (the SII rejected the stored clave — changed or blocked — and the honorarios sync is paused until it is updated) or `no_configurada`. Also the consent text to show and whether it must be accepted again. Never returns the clave, not even masked. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The clave status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasClaveTributaria"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connection_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "status": "activa",
                    "status_changed_at": "2026-10-30T12:00:04.000Z",
                    "verified_at": "2026-10-30T12:00:04.000Z",
                    "last_used_at": "2026-10-31T06:10:00.000Z",
                    "pending_replacement": null,
                    "last_verification": {
                      "outcome": "verified",
                      "at": "2026-10-30T12:00:04.000Z"
                    },
                    "consent": {
                      "version": "vitrina-mandato-1.0#6.5",
                      "accepted_at": "2026-10-30T12:00:00.000Z",
                      "current": true
                    },
                    "consent_required": false,
                    "consent_text": {
                      "version": "vitrina-mandato-1.0#6.5",
                      "title": "Conectar boletas de honorarios",
                      "points": [
                        "Guardamos la Clave Tributaria de la empresa para leer sus boletas de honorarios desde el SII."
                      ],
                      "authorization": "Autorizo a Vitrina a guardar la Clave Tributaria de la empresa y usarla solo para leer sus boletas de honorarios."
                    },
                    "live": true,
                    "checked_at": "2026-10-31T06:10:00.000Z",
                    "actions": {
                      "connect": false,
                      "update": true,
                      "disconnect": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes": {
      "get": {
        "summary": "List Contrapartes (suppliers and customers)",
        "description": "The Contrapartes directory: every business or person the workspace has money with, created automatically from bank movements and documents. Keyed by RUT, or by name when there is none (e.g. «Meta Ads»). `q` matches the name or a RUT in any punctuation (also without its check digit); `role` filters `supplier` (proveedor), `customer` (cliente) or `both`. Sorted by name; merged-away ones are not listed. Each row carries its movement and document counts. Offset-paginated: `meta.pagination.total` counts every match. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Name, or RUT in any punctuation (body-only too)."
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "supplier",
                "customer",
                "both"
              ],
              "description": "supplier = proveedor, customer = cliente, both = ambos."
            },
            "required": false,
            "name": "role",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "activity"
              ],
              "description": "`name` (default) A–Z; `activity` = most movements + documents first."
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The Contrapartes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasContraparte"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "name": "Insumos Médicos Ejemplo SpA",
                      "rut": "76123456-7",
                      "rut_formatted": "76.123.456-7",
                      "roles": [
                        "supplier"
                      ],
                      "source": "movement",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 14,
                      "document_count": 6,
                      "merged_into_id": null,
                      "payment_terms_days": null,
                      "needs_name": false,
                      "payment_terms_source": null,
                      "created_at": "2026-10-01T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}": {
      "get": {
        "summary": "Get a Contraparte",
        "description": "One Contraparte with its roles, its optional links (a Contact, a paciente) and its movement and document counts. A merged-away id still answers, with `merged_into_id` set to the one that remained. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Contraparte",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparte"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "name": "Insumos Médicos Ejemplo SpA",
                    "rut": "76123456-7",
                    "rut_formatted": "76.123.456-7",
                    "roles": [
                      "supplier"
                    ],
                    "source": "movement",
                    "contact": null,
                    "clinic_patient": null,
                    "is_payroll": false,
                    "movement_count": 14,
                    "document_count": 6,
                    "merged_into_id": null,
                    "payment_terms_days": null,
                    "needs_name": false,
                    "payment_terms_source": null,
                    "created_at": "2026-10-01T14:03:00.000Z",
                    "updated_at": "2026-10-30T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Rename a Contraparte, or link or unlink it",
        "description": "Send only what changes: `name` renames it (the RUT and the matching key never change, so the next sync still finds it); `contact_id` / `clinic_patient_id` link it to a Contact or a paciente of this workspace, `null` unlinks (nothing on the Contact or paciente changes). A merged contact or paciente resolves to the one that remained. A merged-away Contraparte answers 409. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteUpdateBody"
              },
              "example": {
                "name": "Insumos Médicos Ejemplo Ltda."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The Contraparte",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparte"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "name": "Insumos Médicos Ejemplo Ltda.",
                    "rut": "76123456-7",
                    "rut_formatted": "76.123.456-7",
                    "roles": [
                      "supplier"
                    ],
                    "source": "movement",
                    "contact": null,
                    "clinic_patient": null,
                    "is_payroll": false,
                    "movement_count": 14,
                    "document_count": 6,
                    "merged_into_id": null,
                    "payment_terms_days": null,
                    "needs_name": false,
                    "payment_terms_source": null,
                    "created_at": "2026-10-01T14:03:00.000Z",
                    "updated_at": "2026-10-30T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/merge": {
      "post": {
        "summary": "Merge a duplicate Contraparte into this one",
        "description": "Merges `merge_id` into `{id}`: every movement, document and link moves to the survivor, which keeps its name and links and takes the other's links only where it has none; roles combine. When only `merge_id` has a RUT, that one is kept instead (`survivor.id` says which). Two different RUTs answer 409. Undoable: `merge_id` goes to `POST /finanzas/contrapartes/merges/{mergeId}/undo`; audited. The merged one stays as a tombstone so the next sync resolves to the survivor and never recreates it. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteMergeBody"
              },
              "example": {
                "merge_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merge",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteMergeResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "survivor": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "name": "Insumos Médicos Ejemplo SpA",
                      "rut": "76123456-7",
                      "rut_formatted": "76.123.456-7",
                      "roles": [
                        "supplier"
                      ],
                      "source": "movement",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 14,
                      "document_count": 6,
                      "merged_into_id": null,
                      "payment_terms_days": null,
                      "needs_name": false,
                      "payment_terms_source": null,
                      "created_at": "2026-10-01T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    },
                    "merged_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e",
                    "merge_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4aaa",
                    "moved": {
                      "movements": 3,
                      "documents": 2
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/duplicates": {
      "get": {
        "summary": "Proposed Contraparte duplicates («Unir duplicados»)",
        "description": "Pairs that look like one business or person: similar names where at least one has no RUT (same normalised name with a known RUT is merged automatically and appears in Hecho por Vitrina instead). `keep` is the one that stays (the one with a RUT, else the more active), `merge` is merged into it, `moves` says how many movements and documents move. Pairs already rejected (`dismiss`) or undone are not proposed. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The proposals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteDuplicates"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "total": 1,
                    "items": [
                      {
                        "keep": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                          "name": "ADOLFO CERDA ALVAREZ",
                          "rut": "14432869-8",
                          "rut_formatted": "14.432.869-8",
                          "movement_count": 1,
                          "document_count": 13
                        },
                        "merge": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e",
                          "name": "Adolfo Cerda A",
                          "rut": null,
                          "rut_formatted": null,
                          "movement_count": 1,
                          "document_count": 0
                        },
                        "score": 0.9,
                        "moves": {
                          "movements": 1,
                          "documents": 0
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/duplicates/merge": {
      "post": {
        "summary": "Accept a proposed duplicate: merge it (undoable)",
        "description": "Same as `POST /finanzas/contrapartes/{id}/merge` (`keep_id` = the path id) but recorded as a review merge. Reversible: it shows in Hecho por Vitrina with «Deshacer» and `merge_id` can be passed to `POST /finanzas/contrapartes/merges/{mergeId}/undo`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteDuplicateMergeBody"
              },
              "example": {
                "keep_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                "merge_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merge",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteMergeResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "survivor": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "name": "Insumos Médicos Ejemplo SpA",
                      "rut": "76123456-7",
                      "rut_formatted": "76.123.456-7",
                      "roles": [
                        "supplier"
                      ],
                      "source": "movement",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 14,
                      "document_count": 6,
                      "merged_into_id": null,
                      "payment_terms_days": null,
                      "needs_name": false,
                      "payment_terms_source": null,
                      "created_at": "2026-10-01T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    },
                    "merged_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e",
                    "merge_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4aaa",
                    "moved": {
                      "movements": 1,
                      "documents": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/contrapartes/duplicates/dismiss": {
      "post": {
        "summary": "Reject a proposed duplicate («No son la misma»)",
        "description": "The two are different: the pair is never proposed again and never merged automatically. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteDuplicateDismissBody"
              },
              "example": {
                "a_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                "b_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "a_id": {
                          "type": "string"
                        },
                        "b_id": {
                          "type": "string"
                        },
                        "dismissed": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "a_id",
                        "b_id",
                        "dismissed"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "a_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "b_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e",
                    "dismissed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/contrapartes/merges": {
      "get": {
        "summary": "Contrapartes merged in a month («Contrapartes unidas»)",
        "description": "Every merge of the month (Santiago), automatic or by a person, newest first, with `undoable` and `undone_at`. The same list rides on `GET /finanzas/automatic-actions` as `contraparte_merges`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Santiago month, `YYYY-MM`.",
              "example": "2026-11"
            },
            "required": true,
            "name": "month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The merges",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasContraparteMergeView"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4aaa",
                      "origin": "same_name",
                      "actor": "system",
                      "label": "Alldental → ALLDENTAL SPA",
                      "survivor": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                        "name": "ALLDENTAL SPA"
                      },
                      "merged": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a9e",
                        "name": "Alldental"
                      },
                      "moved": {
                        "movements": 0,
                        "documents": 1
                      },
                      "reason": "Mismo nombre y un RUT conocido",
                      "created_at": "2026-11-07T14:03:00.000Z",
                      "undone_at": null,
                      "undoable": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/merges/{mergeId}/undo": {
      "post": {
        "summary": "Undo a Contraparte merge («Deshacer»)",
        "description": "Puts back on the original Contraparte exactly the movements, documents, rules and aliases the merge moved (not what a person moved since), restores its links, and makes it live again. The pair is not merged automatically again. 409 when already undone or when the survivor was merged away since (undo that one first). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "mergeId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The undo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteUndoMergeResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "merge_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4aaa",
                    "survivor": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "name": "Insumos Médicos Ejemplo SpA",
                      "rut": "76123456-7",
                      "rut_formatted": "76.123.456-7",
                      "roles": [
                        "supplier"
                      ],
                      "source": "movement",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 14,
                      "document_count": 6,
                      "merged_into_id": null,
                      "payment_terms_days": null,
                      "needs_name": false,
                      "payment_terms_source": null,
                      "created_at": "2026-10-01T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    },
                    "restored": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "name": "Alldental",
                      "rut": "76123456-7",
                      "rut_formatted": "76.123.456-7",
                      "roles": [
                        "supplier"
                      ],
                      "source": "movement",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 14,
                      "document_count": 6,
                      "merged_into_id": null,
                      "payment_terms_days": null,
                      "needs_name": false,
                      "payment_terms_source": null,
                      "created_at": "2026-10-01T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    },
                    "moved_back": {
                      "movements": 0,
                      "documents": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/overview": {
      "get": {
        "summary": "The Contraparte page: balance, terms, linked accounts, rules",
        "description": "Everything on a Contraparte page except its timeline. `balance.payable` = what the workspace owes it (open purchases, honorarios, gastos); `balance.receivable` = what it owes the workspace (open sales). An open document counts its total minus its live reconciliation links; voided ones, ones no longer at the SII, notas de crédito and taxes are left out. Overdue = due before `as_of` (today, America/Santiago). `terms` gives its own terms and the effective terms per class it has documents in. A document is due its issue date plus the payment terms in days: the Contraparte's own terms, else the workspace default of its class (suppliers = compras, customers = ventas, honorarios, expenses = gastos; built-in 30 / 30 / 0 / 0). Changing terms recomputes the due date of every open document it covers; a paid one keeps its due date, and so does one whose source printed a due date. Taxes and notas de crédito have none. `aliases` are its linked RUTs and bank accounts (accounts masked) and the card / automatic-debit descriptors learned from «Por resolver» (`kind: descriptor`). `rules` are the active and paused rules that name it. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContrapartePage"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "contraparte": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6a01",
                      "name": "Laboratorio Dental Andes Ltda",
                      "rut": "78123456-5",
                      "rut_formatted": "78.123.456-5",
                      "roles": [
                        "supplier"
                      ],
                      "source": "document",
                      "contact": null,
                      "clinic_patient": null,
                      "is_payroll": false,
                      "movement_count": 9,
                      "document_count": 12,
                      "merged_into_id": null,
                      "payment_terms_days": 45,
                      "payment_terms_source": "manual",
                      "created_at": "2026-09-02T14:03:00.000Z",
                      "updated_at": "2026-10-30T08:00:00.000Z"
                    },
                    "balance": {
                      "payable": {
                        "open_clp": 2380000,
                        "overdue_clp": 1190000,
                        "open_count": 2,
                        "overdue_count": 1,
                        "oldest_overdue_due_date": "2026-10-20",
                        "next_due_date": "2026-11-19"
                      },
                      "receivable": {
                        "open_clp": 0,
                        "overdue_clp": 0,
                        "open_count": 0,
                        "overdue_count": 0,
                        "oldest_overdue_due_date": null,
                        "next_due_date": null
                      }
                    },
                    "terms": {
                      "days": 45,
                      "by_class": [
                        {
                          "class": "suppliers",
                          "label": "Proveedores",
                          "days": 45,
                          "source": "contraparte",
                          "origin": "manual"
                        }
                      ]
                    },
                    "aliases": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b01",
                        "kind": "rut",
                        "display": "16.123.456-7",
                        "created_at": "2026-10-30T12:00:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b02",
                        "kind": "bank_account",
                        "display": "•••• 5678",
                        "created_at": "2026-10-30T12:01:00.000Z"
                      }
                    ],
                    "rules": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6c01",
                        "name": null,
                        "label": "Documentos de Laboratorio Dental Andes Ltda → Costos directos › Laboratorio",
                        "target": "document",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6a01",
                          "name": "Laboratorio Dental Andes Ltda",
                          "rut": "78.123.456-5"
                        },
                        "text_contains": null,
                        "amount_min_clp": null,
                        "amount_max_clp": null,
                        "direction": null,
                        "document_kind": null,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                          "name": "Laboratorio",
                          "path": "Costos directos › Laboratorio",
                          "kind": "costo",
                          "archived": false
                        },
                        "status": "active",
                        "run_count": 31,
                        "last_applied_at": "2026-10-29T08:00:00.000Z",
                        "created_at": "2026-09-01T12:00:00.000Z",
                        "updated_at": "2026-10-29T08:00:00.000Z"
                      }
                    ],
                    "open_case": {
                      "id": "0190a3c2-bf70-7182-d394-0f1a2b3c4d5e"
                    },
                    "as_of": "2026-10-30"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/timeline": {
      "get": {
        "summary": "A Contraparte's movements and documents, newest first",
        "description": "Its documents and its movements — those that name it, and those a live reconciliation link ties to one of its documents or to it as a third party — ordered by date, newest first. A movement item carries the Movimientos object (its `state`, `assigned_clp`, `pending_clp`); a document item carries the Documentos object plus `paid_clp`, `outstanding_clp` and `overdue`. `type` keeps only one kind. Offset-paginated (`meta.pagination.total`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "document",
                "movement"
              ],
              "description": "Only documents or only movements."
            },
            "required": false,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The timeline",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasContraparteTimelineItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "type": "movement",
                      "date": "2026-10-28",
                      "movement": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6e01",
                        "date": "2026-10-28",
                        "direction": "outbound",
                        "amount_clp": 700000,
                        "currency": "CLP",
                        "original": null,
                        "description": "TRANSF A JUAN PEREZ",
                        "counterparty_name": "Juan Pérez",
                        "counterparty_rut_formatted": "16.123.456-7",
                        "contraparte": null,
                        "bank_account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                          "name": "Cuenta corriente",
                          "institution_name": "Banco de Chile",
                          "number_masked": "•••• 4821",
                          "currency": "CLP"
                        },
                        "state": "reconciled",
                        "assigned_clp": 700000,
                        "pending_clp": 0,
                        "resolved_by": "match",
                        "source": "bank_feed",
                        "operation_id": null,
                        "payer": null,
                        "comment_count": 0,
                        "ads_document_id": null,
                        "card_processor": null,
                        "generic_label": null,
                        "created_at": "2026-10-28T09:00:00.000Z"
                      }
                    },
                    {
                      "type": "document",
                      "date": "2026-10-05",
                      "document": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6d01",
                        "kind": "purchase",
                        "source": "sii",
                        "dte_type": 33,
                        "dte_type_label": "Factura electrónica",
                        "is_credit_note": false,
                        "folio": "4521",
                        "issue_date": "2026-10-05",
                        "period": "2026-10",
                        "received_on": "2026-10-06",
                        "issuer_rut_formatted": "78.123.456-5",
                        "receiver_rut_formatted": "76.543.219-4",
                        "counterparty_name": "LABORATORIO DENTAL ANDES LTDA",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6a01",
                          "name": "Laboratorio Dental Andes Ltda",
                          "rut_formatted": "78.123.456-5"
                        },
                        "amounts": {
                          "net": 1000000,
                          "exempt": 0,
                          "iva": 190000,
                          "other_taxes": 0,
                          "total": 1190000
                        },
                        "due_date": "2026-11-19",
                        "payment_state": "parcial",
                        "sii_estado_contable": "REGISTRO",
                        "missing_at_sii": false,
                        "voided": false,
                        "adjusts": null,
                        "created_at": "2026-10-06T05:31:12.000Z",
                        "updated_at": "2026-10-28T05:31:12.000Z"
                      },
                      "paid_clp": 700000,
                      "outstanding_clp": 490000,
                      "overdue": false
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 21,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/payment-terms": {
      "put": {
        "summary": "Set a Contraparte's payment terms",
        "description": "`days` (0–365) overrides the workspace default of every class for this Contraparte; `null` goes back to the defaults. A document is due its issue date plus the payment terms in days: the Contraparte's own terms, else the workspace default of its class (suppliers = compras, customers = ventas, honorarios, expenses = gastos; built-in 30 / 30 / 0 / 0). Changing terms recomputes the due date of every open document it covers; a paid one keeps its due date, and so does one whose source printed a due date. Taxes and notas de crédito have none. Answers the new terms and how many documents got a new due date. A merged-away Contraparte answers 409. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteTermsBody"
              },
              "example": {
                "days": 45
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new terms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteTermsResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "terms": {
                      "days": 45,
                      "by_class": [
                        {
                          "class": "suppliers",
                          "label": "Proveedores",
                          "days": 45,
                          "source": "contraparte",
                          "origin": "manual"
                        }
                      ]
                    },
                    "recomputed_documents": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/aliases": {
      "post": {
        "summary": "Link a RUT or a bank account to a Contraparte",
        "description": "Linked RUTs and bank accounts: a company Contraparte (RUT 78…) can own a person's account (RUT 16…) it is paid into. The reconciliation engine treats a movement to or from a linked RUT or account as the owning Contraparte's, so that payment matches the company's factura. A RUT or an account belongs to ONE Contraparte per workspace. `kind: rut` takes a RUT in any punctuation (check digit validated; its own RUT answers 400); `kind: bank_account` takes the account number (dashes and leading zeros ignored, 6–30 digits). Already linked here or to another Contraparte → 409 (the message names it). Right after, the reconciliation engine runs over the last 60 days; `reconciled` says what it linked. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasContraparteAliasBody"
              },
              "example": {
                "kind": "rut",
                "value": "16.123.456-7"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Linked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteAliasChange"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "alias": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b01",
                      "kind": "rut",
                      "display": "16.123.456-7",
                      "created_at": "2026-10-30T12:00:00.000Z"
                    },
                    "aliases": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b01",
                        "kind": "rut",
                        "display": "16.123.456-7",
                        "created_at": "2026-10-30T12:00:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b02",
                        "kind": "bank_account",
                        "display": "•••• 5678",
                        "created_at": "2026-10-30T12:01:00.000Z"
                      }
                    ],
                    "reconciled": {
                      "documents": 1,
                      "transfers": 0,
                      "cash_deposits": 0,
                      "payments": 0,
                      "expenses": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contrapartes/{id}/aliases/{aliasId}": {
      "delete": {
        "summary": "Unlink a RUT, bank account or descriptor from a Contraparte",
        "description": "Linked RUTs and bank accounts: a company Contraparte (RUT 78…) can own a person's account (RUT 16…) it is paid into. The reconciliation engine treats a movement to or from a linked RUT or account as the owning Contraparte's, so that payment matches the company's factura. A RUT or an account belongs to ONE Contraparte per workspace. Unlinking stops new movements from matching through it; links already made stay (undo each one with «Deshacer»). Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "aliasId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Unlinked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasContraparteAliasRemoved"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "removed": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b01",
                      "kind": "rut",
                      "display": "16.123.456-7",
                      "created_at": "2026-10-30T12:00:00.000Z"
                    },
                    "aliases": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6b02",
                        "kind": "bank_account",
                        "display": "•••• 5678",
                        "created_at": "2026-10-30T12:01:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/payment-terms": {
      "get": {
        "summary": "Default payment terms per document class",
        "description": "Ajustes › Plazos de pago: the workspace default terms in days for `suppliers` (compras), `customers` (ventas), `honorarios` and `expenses` (gastos). `customized: false` = the built-in default. A document is due its issue date plus the payment terms in days: the Contraparte's own terms, else the workspace default of its class (suppliers = compras, customers = ventas, honorarios, expenses = gastos; built-in 30 / 30 / 0 / 0). Changing terms recomputes the due date of every open document it covers; a paid one keeps its due date, and so does one whose source printed a due date. Taxes and notas de crédito have none. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The defaults",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasPaymentTermsDefault"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "class": "suppliers",
                      "label": "Proveedores",
                      "days": 30,
                      "customized": false,
                      "source": "default",
                      "updated_at": null
                    },
                    {
                      "class": "customers",
                      "label": "Clientes",
                      "days": 30,
                      "customized": false,
                      "source": "default",
                      "updated_at": null
                    },
                    {
                      "class": "honorarios",
                      "label": "Honorarios",
                      "days": 15,
                      "customized": true,
                      "source": "chipax",
                      "updated_at": "2026-10-30T12:00:00.000Z"
                    },
                    {
                      "class": "expenses",
                      "label": "Gastos",
                      "days": 0,
                      "customized": false,
                      "source": "default",
                      "updated_at": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Change default payment terms",
        "description": "Send only the classes that change (0–365 days each). Every open document of a changed class whose Contraparte has no terms of its own gets a new due date. A document is due its issue date plus the payment terms in days: the Contraparte's own terms, else the workspace default of its class (suppliers = compras, customers = ventas, honorarios, expenses = gastos; built-in 30 / 30 / 0 / 0). Changing terms recomputes the due date of every open document it covers; a paid one keeps its due date, and so does one whose source printed a due date. Taxes and notas de crédito have none. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write` (Ajustes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasPaymentTermsBody"
              },
              "example": {
                "honorarios": 15
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The defaults",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasPaymentTermsResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "defaults": [
                      {
                        "class": "suppliers",
                        "label": "Proveedores",
                        "days": 30,
                        "customized": false,
                        "source": "default",
                        "updated_at": null
                      },
                      {
                        "class": "customers",
                        "label": "Clientes",
                        "days": 30,
                        "customized": false,
                        "source": "default",
                        "updated_at": null
                      },
                      {
                        "class": "honorarios",
                        "label": "Honorarios",
                        "days": 15,
                        "customized": true,
                        "source": "chipax",
                        "updated_at": "2026-10-30T12:00:00.000Z"
                      },
                      {
                        "class": "expenses",
                        "label": "Gastos",
                        "days": 0,
                        "customized": false,
                        "source": "default",
                        "updated_at": null
                      }
                    ],
                    "recomputed_documents": 4
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/movements": {
      "get": {
        "summary": "List bank and card movements (Movimientos)",
        "description": "One register of every bank and card movement of the workspace across all its accounts: incoming (`inbound`, abono) and outgoing (`outbound`, cargo), CLP and USD (a USD movement carries its original amount in cents and the CLP equivalent at that day's dólar observado). Each movement has its account, Contraparte, reconciliation `state` (`open` | `partial` | `reconciled`), the amount still to assign (`pending_clp`) and who resolved it. Filters: account, day range, direction, state, amount range (whole CLP), free text, Contraparte. Newest first; offset-paginated; `meta.totals` sums the in and out amounts of every match; `meta.cash_pending` is «efectivo por depositar», the same object the Flujo de caja returns. A movement is cash, never income. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "bank_account_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day, inclusive."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day, inclusive."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ],
              "description": "inbound = abono, outbound = cargo."
            },
            "required": false,
            "name": "direction",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "partial",
                "reconciled"
              ],
              "description": "open = abierto, partial = parcial, reconciled = conciliado."
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 9007199254740991,
              "description": "Whole CLP, inclusive."
            },
            "required": false,
            "name": "amount_min",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 9007199254740991,
              "description": "Whole CLP, inclusive."
            },
            "required": false,
            "name": "amount_max",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over the bank description, counterparty name, operation id and Contraparte name."
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "A merged-away id resolves to its survivor."
            },
            "required": false,
            "name": "contraparte_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The movements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasMovement"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        },
                        "totals": {
                          "type": "object",
                          "properties": {
                            "inbound_clp": {
                              "type": "integer"
                            },
                            "outbound_clp": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "inbound_clp",
                            "outbound_clp"
                          ]
                        },
                        "cash_pending": {
                          "$ref": "#/components/schemas/FinanzasCashPendingSummary"
                        }
                      },
                      "required": [
                        "pagination",
                        "totals",
                        "cash_pending"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                      "date": "2026-10-29",
                      "direction": "outbound",
                      "amount_clp": 1190000,
                      "currency": "CLP",
                      "original": null,
                      "description": "TRANSF A PROVEEDOR EJEMPLO SPA",
                      "counterparty_name": "Proveedor Ejemplo SpA",
                      "counterparty_rut_formatted": "76.123.456-7",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                        "name": "Proveedor Ejemplo SpA",
                        "rut_formatted": "76.123.456-7"
                      },
                      "bank_account": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "name": "Cuenta corriente",
                        "institution_name": "Banco de Chile",
                        "number_masked": "****4821",
                        "currency": "CLP"
                      },
                      "state": "partial",
                      "assigned_clp": 700000,
                      "pending_clp": 490000,
                      "resolved_by": "match",
                      "source": "bank_feed",
                      "operation_id": null,
                      "payer": null,
                      "comment_count": 1,
                      "ads_document_id": null,
                      "card_processor": null,
                      "generic_label": null,
                      "created_at": "2026-10-30T08:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    },
                    "totals": {
                      "inbound_clp": 0,
                      "outbound_clp": 1190000
                    },
                    "cash_pending": {
                      "amount_clp": 11648107,
                      "window_days": 60,
                      "expected_until": "2026-11-06"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/movements/{id}": {
      "get": {
        "summary": "Get a movement with its comments",
        "description": "One movement of the register, with its staff comments (oldest first). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The movement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMovementDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                    "date": "2026-10-29",
                    "direction": "outbound",
                    "amount_clp": 1190000,
                    "currency": "CLP",
                    "original": null,
                    "description": "TRANSF A PROVEEDOR EJEMPLO SPA",
                    "counterparty_name": "Proveedor Ejemplo SpA",
                    "counterparty_rut_formatted": "76.123.456-7",
                    "contraparte": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                      "name": "Proveedor Ejemplo SpA",
                      "rut_formatted": "76.123.456-7"
                    },
                    "bank_account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                      "name": "Cuenta corriente",
                      "institution_name": "Banco de Chile",
                      "number_masked": "****4821",
                      "currency": "CLP"
                    },
                    "state": "partial",
                    "assigned_clp": 700000,
                    "pending_clp": 490000,
                    "resolved_by": "match",
                    "source": "bank_feed",
                    "operation_id": null,
                    "payer": null,
                    "comment_count": 1,
                    "ads_document_id": null,
                    "card_processor": null,
                    "generic_label": null,
                    "created_at": "2026-10-30T08:00:00.000Z",
                    "comments": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                        "body": "Pago de la factura 4521 de Proveedor Ejemplo SpA.",
                        "author": {
                          "user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                          "name": "Camila Rojas"
                        },
                        "created_at": "2026-10-30T15:20:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/movements/{id}/comments": {
      "get": {
        "summary": "List a movement's comments",
        "description": "Staff comments on a movement, with author and time, oldest first. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The comments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasMovementComment"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                      "body": "Pago de la factura 4521 de Proveedor Ejemplo SpA.",
                      "author": {
                        "user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                        "name": "Camila Rojas"
                      },
                      "created_at": "2026-10-30T15:20:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Comment on a movement",
        "description": "Adds a staff comment (1–2000 characters) to a movement; the author is the calling member. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasMovementCommentBody"
              },
              "example": {
                "body": "Pago de la factura 4521 de Proveedor Ejemplo SpA."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The comment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMovementComment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                    "body": "Pago de la factura 4521 de Proveedor Ejemplo SpA.",
                    "author": {
                      "user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                      "name": "Camila Rojas"
                    },
                    "created_at": "2026-10-30T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/bank-accounts": {
      "get": {
        "summary": "List bank accounts with balances (Cuentas)",
        "description": "Each account's current balance and the total in banks today (the Resumen's «Caja»). When the bank feed reports a balance, that is it (`balance_source = feed`); otherwise it is the opening balance plus the movements after its day (`computed`), or `unknown` without one. USD balances convert at the latest dólar observado. `total_clp` sums the known balances and `as_of` is the oldest of their times. These are BANK accounts, not the plan de cuentas. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankAccounts"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "accounts": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "name": "Cuenta corriente",
                        "institution_name": "Banco de Chile",
                        "number_masked": "****4821",
                        "type": "cuenta_corriente",
                        "currency": "CLP",
                        "source": "manual",
                        "has_feed": false,
                        "balance_clp": 8450000,
                        "balance_original": null,
                        "balance_as_of": "2026-10-30T08:00:00.000Z",
                        "balance_source": "computed",
                        "opening_balance": {
                          "amount_minor": 5000000,
                          "on": "2026-09-30"
                        },
                        "opening_balance_editable": true,
                        "movement_count": 312,
                        "agent_sharing": {
                          "shared": true,
                          "alias": "Banco de Chile ··4821",
                          "holder_name": "Clínica Ejemplo SpA",
                          "holder_rut": "76123456-7",
                          "holder_email": null,
                          "account_type": "corriente",
                          "holder_not_company": false
                        },
                        "can_share_with_agent": true
                      }
                    ],
                    "total_clp": 8450000,
                    "as_of": "2026-10-30T08:00:00.000Z",
                    "unknown_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a manual bank or card account",
        "description": "An account without a bank feed (a credit card, a bank Fintoc does not cover): its movements arrive by uploading the bank statement to `/finanzas/bank-accounts/{id}/cartolas`. Shows in Cuentas with `source = manual`; its balance is the opening balance plus the movements. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasManualAccountBody"
              },
              "example": {
                "name": "Visa empresa",
                "institution_name": "Banco de Chile",
                "number": "4111111111114821",
                "type": "tarjeta_credito"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0195f2c8-7d3e-7a10-9b4e-2a6f3c1d8e55",
                    "name": "Visa empresa",
                    "institution_name": "Banco de Chile",
                    "number_masked": "•••• 4821",
                    "type": "tarjeta_credito",
                    "currency": "CLP",
                    "source": "manual",
                    "has_feed": false,
                    "balance_clp": -1250000,
                    "balance_original": null,
                    "balance_as_of": "2026-10-28T00:00:00.000Z",
                    "balance_source": "computed",
                    "opening_balance": {
                      "amount_minor": -400000,
                      "on": "2026-09-30"
                    },
                    "opening_balance_editable": true,
                    "movement_count": 14
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/bank-accounts/{id}/opening-balance": {
      "patch": {
        "summary": "Set a bank account's opening balance",
        "description": "For an account whose feed reports no balance (cartola uploads, Mercado Pago): `amount_minor` is the balance at the END of day `on`, in the account currency smallest unit (CLP pesos, USD cents; negative for a credit line); `null` clears it. 409 when the bank reports the balance. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasOpeningBalanceBody"
              },
              "example": {
                "amount_minor": 5000000,
                "on": "2026-09-30"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                    "name": "Cuenta corriente",
                    "institution_name": "Banco de Chile",
                    "number_masked": "****4821",
                    "type": "cuenta_corriente",
                    "currency": "CLP",
                    "source": "manual",
                    "has_feed": false,
                    "balance_clp": 8450000,
                    "balance_original": null,
                    "balance_as_of": "2026-10-30T08:00:00.000Z",
                    "balance_source": "computed",
                    "opening_balance": {
                      "amount_minor": 5000000,
                      "on": "2026-09-30"
                    },
                    "opening_balance_editable": true,
                    "movement_count": 312,
                    "agent_sharing": {
                      "shared": true,
                      "alias": "Banco de Chile ··4821",
                      "holder_name": "Clínica Ejemplo SpA",
                      "holder_rut": "76123456-7",
                      "holder_email": null,
                      "account_type": "corriente",
                      "holder_not_company": false
                    },
                    "can_share_with_agent": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/bank-accounts/{id}/agent-sharing": {
      "put": {
        "summary": "Share a bank account with the AI agent (or stop)",
        "description": "One bank-account registry (Finanzas 33): the Finanzas bank accounts are the workspace's accounts, and «el agente comparte esta cuenta» is a flag on one of them. `shared: true` makes the AI agent dictate this account for transfers and matches abonos into it (the same registry `GET /tenant-bank-accounts` lists); `false` stops it, keeping the row. The first share needs `holder_name` (400 `VALIDATION_ERROR`, `details.reason = agent_sharing_holder_required`); `holder_rut` defaults to the workspace's SII RUT. 409 `CONFLICT` with `details.reason = agent_sharing_not_supported` for a card, a credit line, a USD account or a number known only by its last digits. Turning sharing on for a holder whose RUT is not the company's needs `confirm_holder_not_company: true` (409 `CONFLICT`, `details.reason = agent_sharing_holder_not_company`, naming the holder); only send the flag after the human has explicitly agreed to share an account held by someone other than the company, and never retry the call with it on your own; an account already shared is never blocked or unshared. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasAgentSharingBody"
              },
              "example": {
                "shared": true,
                "holder_name": "Clínica Ejemplo SpA"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                    "name": "Cuenta corriente",
                    "institution_name": "Banco de Chile",
                    "number_masked": "****4821",
                    "type": "cuenta_corriente",
                    "currency": "CLP",
                    "source": "manual",
                    "has_feed": false,
                    "balance_clp": 8450000,
                    "balance_original": null,
                    "balance_as_of": "2026-10-30T08:00:00.000Z",
                    "balance_source": "computed",
                    "opening_balance": {
                      "amount_minor": 5000000,
                      "on": "2026-09-30"
                    },
                    "opening_balance_editable": true,
                    "movement_count": 312,
                    "agent_sharing": {
                      "shared": true,
                      "alias": "Banco de Chile ··4821",
                      "holder_name": "Clínica Ejemplo SpA",
                      "holder_rut": "76123456-7",
                      "holder_email": null,
                      "account_type": "corriente",
                      "holder_not_company": false
                    },
                    "can_share_with_agent": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/documents": {
      "get": {
        "summary": "List documents (compras, ventas, honorarios, gastos, F29)",
        "description": "The Documentos register: every document the workspace has money on, one list filtered by `kind` — `purchase` (Compras, from the SII registro de compras), `sale` (Ventas: the SII registro de ventas and what was emitted through Vitrina), `honorario`, `manual_expense`, `tax` (F29). Each carries its Contraparte, DTE type, folio, issue date, net / IVA / exempt / total in whole CLP, due date and payment state; a nota de crédito or débito carries the document it adjusts. Filter by `period` (`YYYY-MM`) or `from`/`to`, Contraparte, payment state, DTE type, source, or `q` (a folio, a name or a RUT); `paid_before_vitrina=true` is the «Heredado» filter (Finanzas 50). Newest first, offset-paginated; `meta.counts_by_kind` counts the same filters per kind. The SII documents sync daily in the background. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "purchase",
                "sale",
                "honorario",
                "manual_expense",
                "tax"
              ],
              "description": "The register tab: purchase = Compras, sale = Ventas, honorario = Honorarios, manual_expense = Gastos manuales, tax = F29."
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "sii",
                "vitrina",
                "manual",
                "chipax"
              ]
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "ads"
              ],
              "description": "ads = the documents Vitrina made from Meta / Google card charges (Finanzas 30, «Cargos de publicidad»)."
            },
            "required": false,
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month (`YYYY-MM`) of issue dates. Not with from/to."
            },
            "required": false,
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Issue date from (inclusive)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Issue date to (inclusive)."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contraparte_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "por_pagar",
                "parcial",
                "pagado",
                "vencido_heredado"
              ],
              "description": "`vencido_heredado` is retired (Finanzas 50: those documents are now «Pagado antes de Vitrina», see `paid_before_vitrina`) and only matches rows written before it in a workspace outside that backfill."
            },
            "required": false,
            "name": "payment_state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "«Heredado» (Finanzas 50): `true` lists only the documents «Pagado antes de Vitrina»: from a period closed before Vitrina, still open there, treated as settled and out of the cash projection until reopened (`POST /finanzas/documents/{id}/reopen`)."
            },
            "required": false,
            "name": "paid_before_vitrina",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 999
            },
            "required": false,
            "name": "dte_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "A folio, or the counterparty name or RUT (any punctuation)."
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasDocument"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        },
                        "counts_by_kind": {
                          "type": "object",
                          "properties": {
                            "purchase": {
                              "type": "integer"
                            },
                            "sale": {
                              "type": "integer"
                            },
                            "honorario": {
                              "type": "integer"
                            },
                            "manual_expense": {
                              "type": "integer"
                            },
                            "tax": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "purchase",
                            "sale",
                            "honorario",
                            "manual_expense",
                            "tax"
                          ]
                        }
                      },
                      "required": [
                        "pagination",
                        "counts_by_kind"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "kind": "purchase",
                      "source": "sii",
                      "dte_type": 61,
                      "dte_type_label": "Nota de crédito electrónica",
                      "is_credit_note": true,
                      "folio": "45",
                      "issue_date": "2026-10-10",
                      "period": "2026-10",
                      "received_on": "2026-10-11",
                      "issuer_rut_formatted": "76.086.428-5",
                      "receiver_rut_formatted": "76.543.219-4",
                      "counterparty_name": "Laboratorio Andes SpA",
                      "contraparte": {
                        "id": "0190a3c2-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "name": "Laboratorio Andes SpA",
                        "rut_formatted": "76.086.428-5"
                      },
                      "amounts": {
                        "net": 10000,
                        "exempt": 0,
                        "iva": 1900,
                        "other_taxes": 0,
                        "total": 11900
                      },
                      "due_date": null,
                      "payment_state": "por_pagar",
                      "settlement": null,
                      "sii_estado_contable": "REGISTRO",
                      "missing_at_sii": false,
                      "voided": false,
                      "adjusts": {
                        "id": "0190a3c2-7b3c-7d4e-9f50-6b7c8d9e0f1a",
                        "dte_type": 33,
                        "dte_type_label": "Factura electrónica",
                        "folio": "1001"
                      },
                      "f29": null,
                      "honorario": null,
                      "created_at": "2026-10-30T05:31:12.000Z",
                      "updated_at": "2026-10-30T05:31:12.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    },
                    "counts_by_kind": {
                      "purchase": 1,
                      "sale": 0,
                      "honorario": 0,
                      "manual_expense": 0,
                      "tax": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/f29": {
      "get": {
        "summary": "F29 by period, with the next due date",
        "description": "The monthly IVA declaration (F29) per tax period, read from the SII through the connected sociedad: amount to pay (`amounts.total`), `f29.state` (proposal / in progress / filed / rejected), filing folio and date, rectifications, the IVA / PPM breakdown, the `due_date` and `payment_state` (paid once the tax payment movement reconciles against it). `next_due` is the earliest unpaid F29 still due, or the next period's due date with `amount: null` while it is unknown. Due dates are the 20th of the next month, moved to the next working day. Read-only: Vitrina does not file the F29. `from`/`to` are months (`YYYY-MM`; default the last 12), at most 24. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First tax period (default: 11 months before `to`)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last tax period (default: this month)."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The F29 periods",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasF29Overview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2025-11",
                    "to": "2026-10",
                    "periods": [
                      {
                        "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                        "kind": "tax",
                        "source": "sii",
                        "dte_type": null,
                        "dte_type_label": null,
                        "is_credit_note": false,
                        "folio": null,
                        "issue_date": "2026-09-01",
                        "period": "2026-09",
                        "received_on": null,
                        "issuer_rut_formatted": "76.543.219-4",
                        "receiver_rut_formatted": null,
                        "counterparty_name": "Servicio de Impuestos Internos",
                        "contraparte": null,
                        "amounts": {
                          "net": 0,
                          "exempt": 0,
                          "iva": 0,
                          "other_taxes": 0,
                          "total": 1373000
                        },
                        "due_date": "2026-10-20",
                        "payment_state": "por_pagar",
                        "settlement": null,
                        "sii_estado_contable": null,
                        "missing_at_sii": false,
                        "voided": false,
                        "adjusts": null,
                        "f29": {
                          "period": "2026-09",
                          "state": "filed",
                          "state_label": "Declarado",
                          "filed": true,
                          "amount_is_final": true,
                          "filed_at": "2026-10-14T15:02:11.000Z",
                          "filing_folio": "8123456789",
                          "rectifications": 0,
                          "summary": {
                            "iva_debito": 1900000,
                            "iva_credito": 650000,
                            "iva_retenido": 0,
                            "remanente_anterior": 0,
                            "remanente_siguiente": 0,
                            "ppm": 123000
                          }
                        },
                        "honorario": null,
                        "created_at": "2026-10-30T05:31:12.000Z",
                        "updated_at": "2026-10-30T05:31:12.000Z"
                      }
                    ],
                    "next_due": {
                      "period": "2026-10",
                      "due_date": "2026-11-20",
                      "amount": null,
                      "document_id": null,
                      "state": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/libro": {
      "get": {
        "summary": "Monthly libro de compras or libro de ventas",
        "description": "Monthly totals of one register: `kind=purchase` is the libro de compras, `kind=sale` the libro de ventas. Per month: document count, net, exempt, IVA, other taxes, total and `iva_fiscal` (IVA crédito fiscal on purchases — REGISTRO entries only — or IVA débito fiscal on sales), with a line per DTE type. Notas de crédito subtract; documents the SII no longer lists and voided ones are left out. `from`/`to` are months (`YYYY-MM`; default January → this month), at most 24. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "purchase",
                "sale"
              ],
              "description": "purchase = libro de compras, sale = libro de ventas."
            },
            "required": true,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of the `to` year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: this month)."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The libro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasLibro"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "purchase",
                    "from": "2026-10",
                    "to": "2026-10",
                    "months": [
                      {
                        "period": "2026-10",
                        "count": 2,
                        "net": 90000,
                        "exempt": 0,
                        "iva": 17100,
                        "other_taxes": 0,
                        "total": 107100,
                        "iva_fiscal": 17100,
                        "lines": [
                          {
                            "dte_type": 33,
                            "dte_type_label": "Factura electrónica",
                            "count": 1,
                            "net": 100000,
                            "exempt": 0,
                            "iva": 19000,
                            "other_taxes": 0,
                            "total": 119000
                          },
                          {
                            "dte_type": 61,
                            "dte_type_label": "Nota de crédito electrónica",
                            "count": 1,
                            "net": -10000,
                            "exempt": 0,
                            "iva": -1900,
                            "other_taxes": 0,
                            "total": -11900
                          }
                        ]
                      }
                    ],
                    "totals": {
                      "count": 2,
                      "net": 90000,
                      "exempt": 0,
                      "iva": 17100,
                      "other_taxes": 0,
                      "total": 107100,
                      "iva_fiscal": 17100
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/libro/export": {
      "get": {
        "summary": "Export the libro de compras or ventas (xlsx / csv)",
        "description": "The libro as a spreadsheet: sheet «Resumen» (one row per month and a total) and sheet «Detalle» (one row per document). `format` is `xlsx` (default) or `csv` (the summary only). Same query as the libro. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:export`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "purchase",
                "sale"
              ],
              "description": "purchase = libro de compras, sale = libro de ventas."
            },
            "required": true,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of the `to` year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: this month)."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ]
            },
            "required": false,
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The libro, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/honorarios/summary": {
      "get": {
        "summary": "Monthly honorarios and retención total",
        "description": "The boletas de honorarios of one month (`period`, AAAA-MM; default the current month), received and issued: count, gross, retención and líquido, annulled boletas excluded. `f29_codigo_151_clp` is the retención on the received ones (F29 código 151). `clave_status` other than `activa` means zeros are «unknown», not «none»: boletas sync only with the SII clave connected. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, AAAA-MM. Default: the current month (Chile).",
              "example": "2026-10"
            },
            "required": false,
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The month",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasHonorariosSummary"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": "2026-10",
                    "currency": "CLP",
                    "received": {
                      "count": 12,
                      "gross_clp": 18400000,
                      "retention_clp": 2806000,
                      "net_clp": 15594000
                    },
                    "issued": {
                      "count": 0,
                      "gross_clp": 0,
                      "retention_clp": 0,
                      "net_clp": 0
                    },
                    "f29_codigo_151_clp": 2806000,
                    "clave_status": "activa"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/{id}": {
      "get": {
        "summary": "Get a document",
        "description": "One document with its Contraparte, amounts, payment state, the document it adjusts (a nota de crédito / débito) and the notes that adjust it (`adjusted_by`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                    "kind": "purchase",
                    "source": "sii",
                    "dte_type": 61,
                    "dte_type_label": "Nota de crédito electrónica",
                    "is_credit_note": true,
                    "folio": "45",
                    "issue_date": "2026-10-10",
                    "period": "2026-10",
                    "received_on": "2026-10-11",
                    "issuer_rut_formatted": "76.086.428-5",
                    "receiver_rut_formatted": "76.543.219-4",
                    "counterparty_name": "Laboratorio Andes SpA",
                    "contraparte": {
                      "id": "0190a3c2-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "name": "Laboratorio Andes SpA",
                      "rut_formatted": "76.086.428-5"
                    },
                    "amounts": {
                      "net": 10000,
                      "exempt": 0,
                      "iva": 1900,
                      "other_taxes": 0,
                      "total": 11900
                    },
                    "due_date": null,
                    "payment_state": "por_pagar",
                    "settlement": null,
                    "sii_estado_contable": "REGISTRO",
                    "missing_at_sii": false,
                    "voided": false,
                    "adjusts": {
                      "id": "0190a3c2-7b3c-7d4e-9f50-6b7c8d9e0f1a",
                      "dte_type": 33,
                      "dte_type_label": "Factura electrónica",
                      "folio": "1001"
                    },
                    "f29": null,
                    "honorario": null,
                    "created_at": "2026-10-30T05:31:12.000Z",
                    "updated_at": "2026-10-30T05:31:12.000Z",
                    "adjusted_by": [],
                    "description": null,
                    "files": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a manual document",
        "description": "Corrects a document uploaded or forwarded to Vitrina (source `manual`): amount, date, issuer RUT / name, Contraparte, type, folio, description. Audited with before / after. SII documents are the SII's word: 409 `FINANZAS_DOCUMENT_CONFLICT` `not_manual`. The amount cannot change while a reconciliation link or a classification holds it (`amount_in_use`). Changing the amount, date or type of a document in a closed month (old or new date) needs `?adjustment_reason=` (else 409 `FINANZAS_PERIOD_CLOSED`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "total_clp": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "issue_date": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "issuer_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "issuer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "contraparte_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "dte_type": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999
                  },
                  "folio": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{1,12}$"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "total_clp": 850000,
                "issue_date": "2026-10-02"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-ae6f-7071-c283-9e0f1a2b3c4d",
                    "kind": "manual_expense",
                    "source": "manual",
                    "dte_type": null,
                    "dte_type_label": null,
                    "is_credit_note": false,
                    "folio": "1042",
                    "issue_date": "2026-10-01",
                    "period": "2026-10",
                    "received_on": null,
                    "issuer_rut_formatted": "76.086.428-5",
                    "receiver_rut_formatted": null,
                    "counterparty_name": "Inmobiliaria Los Andes SpA",
                    "contraparte": {
                      "id": "0190a3c2-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "name": "Inmobiliaria Los Andes SpA",
                      "rut_formatted": "76.086.428-5"
                    },
                    "amounts": {
                      "net": 0,
                      "exempt": 0,
                      "iva": 0,
                      "other_taxes": 0,
                      "total": 850000
                    },
                    "due_date": null,
                    "payment_state": "por_pagar",
                    "settlement": null,
                    "sii_estado_contable": null,
                    "missing_at_sii": false,
                    "voided": false,
                    "adjusts": null,
                    "f29": null,
                    "honorario": null,
                    "created_at": "2026-10-30T05:31:12.000Z",
                    "updated_at": "2026-10-30T05:31:12.000Z",
                    "adjusted_by": [],
                    "description": "Arriendo oficina octubre",
                    "files": [
                      {
                        "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                        "status": "confirmed",
                        "channel": "upload",
                        "filename": "boleta-arriendo-octubre.pdf",
                        "mime_type": "application/pdf",
                        "byte_size": 184233,
                        "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                        "url_expires_at": "2026-10-30T15:10:00.000Z",
                        "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                        "case_id": null,
                        "email": null,
                        "extraction_status": "complete",
                        "extracted": {
                          "total_clp": 850000,
                          "currency": "CLP",
                          "amount_original": null,
                          "fx_rate_clp": null,
                          "issue_date": "2026-10-01",
                          "issuer_rut": "76086428-5",
                          "issuer_name": "Inmobiliaria Los Andes SpA",
                          "dte_type": null,
                          "folio": "1042",
                          "description": "Arriendo oficina octubre"
                        },
                        "missing": [],
                        "error": null,
                        "document_id": "0190a3c2-ae6f-7071-c283-9e0f1a2b3c4d",
                        "created_at": "2026-10-30T15:00:00.000Z",
                        "confirmed_at": "2026-10-30T15:02:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/uploads": {
      "get": {
        "summary": "List uploaded and forwarded files",
        "description": "Every file received for Finanzas, newest first: uploads and emails forwarded to the workspace address. `status=pending_review` is «Por revisar» (read, waiting for a person: an upload not yet confirmed, or an email whose reading was incomplete). Each carries a short-lived signed `url`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending_review",
                "confirmed",
                "discarded",
                "failed"
              ],
              "description": "`pending_review` = «Por revisar»."
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasDocumentFile"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                      "status": "pending_review",
                      "channel": "upload",
                      "filename": "boleta-arriendo-octubre.pdf",
                      "mime_type": "application/pdf",
                      "byte_size": 184233,
                      "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                      "url_expires_at": "2026-10-30T15:10:00.000Z",
                      "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                      "case_id": null,
                      "email": null,
                      "extraction_status": "complete",
                      "extracted": {
                        "total_clp": 850000,
                        "currency": "CLP",
                        "amount_original": null,
                        "fx_rate_clp": null,
                        "issue_date": "2026-10-01",
                        "issuer_rut": "76086428-5",
                        "issuer_name": "Inmobiliaria Los Andes SpA",
                        "dte_type": null,
                        "folio": "1042",
                        "description": "Arriendo oficina octubre"
                      },
                      "missing": [],
                      "error": null,
                      "document_id": null,
                      "created_at": "2026-10-30T15:00:00.000Z",
                      "confirmed_at": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a document that is not in the SII",
        "description": "A receipt, a foreign invoice (Meta Ads, software), a boleta from someone without e-boleta, a contract: a PDF or a photo (JPG, PNG, WEBP, HEIC; 15 MB at most), optionally onto a movement (`movement_id`) or a «Por resolver» case (`case_id`). The file is stored privately and READ: total, date, issuer RUT and name, type, folio, currency (USD converts at the dólar observado of its day). The answer is the file in `pending_review` with `extracted` and what is `missing`: nothing is in the ledger until it is confirmed (`POST …/uploads/{id}/confirm`). 422 `FINANZAS_DOCUMENT_INVALID` (`unsupported_file`, `file_mismatch`, `file_too_large`).\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "movement_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Attach it to this movement (the proposal favours it)."
                  },
                  "case_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Attach it to this «Por resolver» case."
                  }
                }
              },
              "example": {
                "file": "(binary)",
                "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "movement_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Attach it to this movement (the proposal favours it)."
                  },
                  "case_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Attach it to this «Por resolver» case."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "boleta-arriendo-octubre.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64",
                "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The file, read and waiting for review",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentFile"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                    "status": "pending_review",
                    "channel": "upload",
                    "filename": "boleta-arriendo-octubre.pdf",
                    "mime_type": "application/pdf",
                    "byte_size": 184233,
                    "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                    "url_expires_at": "2026-10-30T15:10:00.000Z",
                    "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                    "case_id": null,
                    "email": null,
                    "extraction_status": "complete",
                    "extracted": {
                      "total_clp": 850000,
                      "currency": "CLP",
                      "amount_original": null,
                      "fx_rate_clp": null,
                      "issue_date": "2026-10-01",
                      "issuer_rut": "76086428-5",
                      "issuer_name": "Inmobiliaria Los Andes SpA",
                      "dte_type": null,
                      "folio": "1042",
                      "description": "Arriendo oficina octubre"
                    },
                    "missing": [],
                    "error": null,
                    "document_id": null,
                    "created_at": "2026-10-30T15:00:00.000Z",
                    "confirmed_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/documents/uploads/{id}": {
      "get": {
        "summary": "Get an uploaded or forwarded file",
        "description": "One file with what was read from it and a fresh signed `url`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentFile"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                    "status": "pending_review",
                    "channel": "upload",
                    "filename": "boleta-arriendo-octubre.pdf",
                    "mime_type": "application/pdf",
                    "byte_size": 184233,
                    "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                    "url_expires_at": "2026-10-30T15:10:00.000Z",
                    "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                    "case_id": null,
                    "email": null,
                    "extraction_status": "complete",
                    "extracted": {
                      "total_clp": 850000,
                      "currency": "CLP",
                      "amount_original": null,
                      "fx_rate_clp": null,
                      "issue_date": "2026-10-01",
                      "issuer_rut": "76086428-5",
                      "issuer_name": "Inmobiliaria Los Andes SpA",
                      "dte_type": null,
                      "folio": "1042",
                      "description": "Arriendo oficina octubre"
                    },
                    "missing": [],
                    "error": null,
                    "document_id": null,
                    "created_at": "2026-10-30T15:00:00.000Z",
                    "confirmed_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard a file",
        "description": "Throws away a `pending_review` file: the stored file is deleted, the row stays for the log. 409 `FINANZAS_DOCUMENT_CONFLICT` when it is already a document. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Discarded"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/uploads/{id}/confirm": {
      "post": {
        "summary": "Confirm a file as a document",
        "description": "Turns a `pending_review` file into a document (source `manual`) with its Contraparte, and returns the «Por resolver» case it now sits in (`case_id`) so the match can be confirmed. Every body field overrides what was read (`edited` reports the changes; the audit log keeps both). Contraparte: `contraparte_id` > the issuer RUT > the movement's or case's Contraparte > the issuer name. A factura (types 33/34/46/56/61…) with an issuer RUT is a purchase (`kind: purchase`) and dedupes with the SII register on (RUT, type, folio); anything else is a `manual_expense`. A file whose (RUT, type, folio) is already in the ledger, or with `document_id`, is ATTACHED to that document (`outcome: attached`). 422 `FINANZAS_DOCUMENT_INVALID` (`incomplete` with `details.missing`, `amount_invalid`, `rut_invalid`); 409 `FINANZAS_DOCUMENT_CONFLICT` (`already_confirmed`, `already_discarded`, `duplicate`). A new document dated in a closed month needs `?adjustment_reason=` (else 409 `FINANZAS_PERIOD_CLOSED`, nothing written) and is logged as an «ajuste posterior al cierre». Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "total_clp": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "issue_date": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "issuer_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "issuer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "contraparte_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "dte_type": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999
                  },
                  "folio": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{1,12}$"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "net_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "iva_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "document_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "Attach the file to this existing document instead of creating one."
                  }
                },
                "additionalProperties": false,
                "description": "Every field overrides what was read; leave it out to keep the reading."
              },
              "example": {
                "total_clp": 850000,
                "issue_date": "2026-10-01",
                "description": "Arriendo oficina octubre"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentFileConfirmResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "created",
                    "document": {
                      "id": "0190a3c2-ae6f-7071-c283-9e0f1a2b3c4d",
                      "kind": "manual_expense",
                      "source": "manual",
                      "dte_type": null,
                      "dte_type_label": null,
                      "is_credit_note": false,
                      "folio": "1042",
                      "issue_date": "2026-10-01",
                      "period": "2026-10",
                      "received_on": null,
                      "issuer_rut_formatted": "76.086.428-5",
                      "receiver_rut_formatted": null,
                      "counterparty_name": "Inmobiliaria Los Andes SpA",
                      "contraparte": {
                        "id": "0190a3c2-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "name": "Inmobiliaria Los Andes SpA",
                        "rut_formatted": "76.086.428-5"
                      },
                      "amounts": {
                        "net": 0,
                        "exempt": 0,
                        "iva": 0,
                        "other_taxes": 0,
                        "total": 850000
                      },
                      "due_date": null,
                      "payment_state": "por_pagar",
                      "settlement": null,
                      "sii_estado_contable": null,
                      "missing_at_sii": false,
                      "voided": false,
                      "adjusts": null,
                      "f29": null,
                      "honorario": null,
                      "created_at": "2026-10-30T05:31:12.000Z",
                      "updated_at": "2026-10-30T05:31:12.000Z",
                      "adjusted_by": [],
                      "description": "Arriendo oficina octubre",
                      "files": [
                        {
                          "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                          "status": "confirmed",
                          "channel": "upload",
                          "filename": "boleta-arriendo-octubre.pdf",
                          "mime_type": "application/pdf",
                          "byte_size": 184233,
                          "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                          "url_expires_at": "2026-10-30T15:10:00.000Z",
                          "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                          "case_id": null,
                          "email": null,
                          "extraction_status": "complete",
                          "extracted": {
                            "total_clp": 850000,
                            "currency": "CLP",
                            "amount_original": null,
                            "fx_rate_clp": null,
                            "issue_date": "2026-10-01",
                            "issuer_rut": "76086428-5",
                            "issuer_name": "Inmobiliaria Los Andes SpA",
                            "dte_type": null,
                            "folio": "1042",
                            "description": "Arriendo oficina octubre"
                          },
                          "missing": [],
                          "error": null,
                          "document_id": "0190a3c2-ae6f-7071-c283-9e0f1a2b3c4d",
                          "created_at": "2026-10-30T15:00:00.000Z",
                          "confirmed_at": "2026-10-30T15:02:00.000Z"
                        }
                      ]
                    },
                    "file": {
                      "id": "0190a3c2-8c4d-7e5f-a061-7c8d9e0f1a2b",
                      "status": "confirmed",
                      "channel": "upload",
                      "filename": "boleta-arriendo-octubre.pdf",
                      "mime_type": "application/pdf",
                      "byte_size": 184233,
                      "url": "https://files.example.test/tenants/…/finanzas/files/0190a3c2.pdf?X-Amz-Signature=…",
                      "url_expires_at": "2026-10-30T15:10:00.000Z",
                      "movement_id": "0190a3c2-9d5e-7f60-b172-8d9e0f1a2b3c",
                      "case_id": null,
                      "email": null,
                      "extraction_status": "complete",
                      "extracted": {
                        "total_clp": 850000,
                        "currency": "CLP",
                        "amount_original": null,
                        "fx_rate_clp": null,
                        "issue_date": "2026-10-01",
                        "issuer_rut": "76086428-5",
                        "issuer_name": "Inmobiliaria Los Andes SpA",
                        "dte_type": null,
                        "folio": "1042",
                        "description": "Arriendo oficina octubre"
                      },
                      "missing": [],
                      "error": null,
                      "document_id": "0190a3c2-ae6f-7071-c283-9e0f1a2b3c4d",
                      "created_at": "2026-10-30T15:00:00.000Z",
                      "confirmed_at": "2026-10-30T15:02:00.000Z"
                    },
                    "case_id": "0190a3c2-bf70-7182-d394-0f1a2b3c4d5e",
                    "edited": {}
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/documents/{id}/reopen": {
      "post": {
        "summary": "Reopen a settled document («Reabrir»)",
        "description": "Undoes what settled a document without a bank link (`settlement`): «Pagado antes de Vitrina» (a document from a period closed before Vitrina, still open there) or a person's «Marcar pagada». From then on it is an open document like any other: its state follows its reconciliation links, it enters the cash projection or «Vencidas sin pago visto», and the import never settles it again. Resultado and the Contador do not move (they never read the payment state). Idempotent: an open document answers `reopened: false`. Reopening a document a person marked anulado in a closed month needs `?adjustment_reason=` (else 409 `FINANZAS_PERIOD_CLOSED`). Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reopened (or already open)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentReopenResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reopened": true,
                    "document": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a5b",
                      "kind": "purchase",
                      "source": "sii",
                      "dte_type": 33,
                      "dte_type_label": "Factura electrónica",
                      "is_credit_note": false,
                      "folio": "45",
                      "issue_date": "2026-10-10",
                      "period": "2026-10",
                      "received_on": "2026-10-11",
                      "issuer_rut_formatted": "76.086.428-5",
                      "receiver_rut_formatted": "76.543.219-4",
                      "counterparty_name": "Laboratorio Andes SpA",
                      "contraparte": {
                        "id": "0190a3c2-6a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "name": "Laboratorio Andes SpA",
                        "rut_formatted": "76.086.428-5"
                      },
                      "amounts": {
                        "net": 10000,
                        "exempt": 0,
                        "iva": 1900,
                        "other_taxes": 0,
                        "total": 11900
                      },
                      "due_date": null,
                      "payment_state": "por_pagar",
                      "settlement": null,
                      "sii_estado_contable": "REGISTRO",
                      "missing_at_sii": false,
                      "voided": false,
                      "adjusts": null,
                      "f29": null,
                      "honorario": null,
                      "created_at": "2026-10-30T05:31:12.000Z",
                      "updated_at": "2026-10-30T05:31:12.000Z",
                      "adjusted_by": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/inbox": {
      "get": {
        "summary": "The workspace's forwarding address",
        "description": "The address to forward receipts and invoices to (`finanzas+<token>@vitrinadev.com`). Mail is accepted only from an active member of the workspace (their login email) who holds `finanzas:write` (a read-only seat such as the Contador is dropped) and whose provider signed it (DKIM / SPF / DMARC); anything else is dropped and logged. Each PDF or photo attached (or the email body, when there is none) is read; a complete reading becomes a document at once, an incomplete one waits in `GET /finanzas/documents/uploads?status=pending_review`. Created on first read. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The address",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasInbox"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "address": "finanzas+k7m2q9xw4pha@vitrinadev.com",
                    "accepts_from": "workspace_members",
                    "created_at": "2026-10-30T15:00:00.000Z",
                    "rotated_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/inbox/rotate": {
      "post": {
        "summary": "Replace the forwarding address",
        "description": "A new address; the old one stops working at once (mail to it is dropped). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new address",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasInbox"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "address": "finanzas+k7m2q9xw4pha@vitrinadev.com",
                    "accepts_from": "workspace_members",
                    "created_at": "2026-10-30T15:00:00.000Z",
                    "rotated_at": "2026-10-30T16:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/cartola-formats": {
      "get": {
        "summary": "List the statement formats read with no setup",
        "description": "The banks whose cartola (CSV / xlsx) parses without a column mapping. Any other layout whose header is recognisable still parses; a layout that is not asks for a one-time mapping on upload. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The formats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasCartolaFormat"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "key": "banco_de_chile",
                      "label": "Banco de Chile",
                      "bank": "Banco de Chile"
                    },
                    {
                      "key": "santander",
                      "label": "Santander",
                      "bank": "Banco Santander"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/bank-accounts/{id}/cartolas/preview": {
      "post": {
        "summary": "Preview a statement before importing it",
        "description": "Reads the statement and says what an import would do, writing nothing: the format, the movements (with which are already in Vitrina), the date range, the balances and the **continuity check** (the file's opening balance against the account's balance in Vitrina; `continuity.status = gap` carries the amount and the dates). When the layout is not recognised the answer is `status = needs_mapping` with the header row, a sample and what was recognised: map the columns and send the preview again with `mapping`. Only for a manual account (409 `details.reason = cartola_account_has_feed` otherwise).\n\n`multipart/form-data` (`file`, optional `mapping` as JSON text) or the JSON file shape. Amounts are the account currency smallest unit (CLP pesos, USD cents). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The statement: .csv, .xlsx or .xls, at most 20 MB."
                  }
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "mapping": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "columns": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "description": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "debit": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "credit": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "amount": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "direction": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "balance": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "document": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          }
                        },
                        "description": "role → the header text of that column in the file. `date`, `description` and either `amount` or `debit`/`credit` are required."
                      },
                      "date_format": {
                        "type": "string",
                        "enum": [
                          "dmy",
                          "ymd",
                          "mdy"
                        ],
                        "description": "Order of the day, month and year in text dates (default dmy: 31/10/2026). Real spreadsheet dates need no format."
                      },
                      "amount_sign": {
                        "type": "string",
                        "enum": [
                          "positive_inbound",
                          "positive_outbound"
                        ],
                        "description": "Only for a single signed `amount` column. Default: positive = abono for an account, positive = cargo (a purchase) for a credit card."
                      }
                    },
                    "required": [
                      "columns"
                    ],
                    "description": "Column mapping for an unknown format (JSON text in multipart). Omit to use the account’s saved mapping, or the automatic reading."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "cartola-octubre.csv",
                "content": "RmVjaGE7RGVzY3JpcGNpw7NuOy4uLg==",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCartolaPreview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "status": "ready",
                    "mapping_needed": null,
                    "format": {
                      "key": "banco_de_chile",
                      "label": "Banco de Chile",
                      "auto": true
                    },
                    "mapping_source": "auto",
                    "header_row": 8,
                    "columns": {
                      "date": "Fecha",
                      "description": "Descripción",
                      "amount": "Monto"
                    },
                    "date_from": "2026-10-01",
                    "date_to": "2026-10-28",
                    "counts": {
                      "rows_read": 14,
                      "new": 14,
                      "duplicate": 0,
                      "rejected": 0,
                      "skipped": 1
                    },
                    "totals": {
                      "inbound_minor": 150000,
                      "outbound_minor": 1000000
                    },
                    "balance": {
                      "has_balance": false,
                      "opening_minor": null,
                      "closing_minor": null,
                      "chain_breaks": []
                    },
                    "continuity": {
                      "status": "not_checked",
                      "reason": "no_balance_in_file",
                      "file_opening_minor": -400000,
                      "file_opening_on": "2026-09-30",
                      "ledger_balance_minor": -400000,
                      "gap_minor": 0,
                      "gap_from": null,
                      "gap_to": null
                    },
                    "rejections": [],
                    "movements": [
                      {
                        "row": 9,
                        "date": "2026-10-02",
                        "direction": "outbound",
                        "amount_minor": 45000,
                        "description": "COMPRA UBER TRIP",
                        "balance_minor": null,
                        "duplicate": false
                      }
                    ],
                    "movements_truncated": false,
                    "currency": "CLP"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/bank-accounts/{id}/cartolas": {
      "get": {
        "summary": "List the statements uploaded to an account",
        "description": "Newest first (up to 50): file name, format, range, counts, balances and any accepted gap. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The uploads",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasCartolaImportRecord"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0195f2c9-0a11-7c20-8d6f-91b7e4a3c2d1",
                      "bank_account_id": "0195f2c8-7d3e-7a10-9b4e-2a6f3c1d8e55",
                      "file_name": "cartola-octubre.csv",
                      "format": "banco_de_chile",
                      "date_from": "2026-10-01",
                      "date_to": "2026-10-28",
                      "rows_read": 14,
                      "rows_new": 14,
                      "rows_duplicate": 0,
                      "rows_rejected": 0,
                      "opening_minor": -400000,
                      "closing_minor": -1250000,
                      "gap_minor": 0,
                      "gap_accepted": false,
                      "created_at": "2026-10-30T14:02:11.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Import a statement into Movimientos",
        "description": "Imports the statement of a manual account: its movements land in Movimientos like any other (`source = cartola`), on their Contraparte. Idempotent: re-uploading a file, or an overlapping one, adds nothing twice (`counts.duplicate`).\n\n**Continuity check.** The file's opening balance must equal the account's balance in Vitrina at the end of the day before the file starts. On a difference the import answers `409 CONFLICT` with `details.reason = cartola_gap` and `details.continuity` (amount, dates) and imports nothing; repeat with `accept_gap = true` to import anyway (the account balance is restated to the bank's). A file whose own balance column does not add up answers `400` with `details.reason = cartola_balance_inconsistent`. The first file of a new account sets the account's opening balance from the file. A file without a balance column (most card statements) is imported without a continuity check (`continuity.status = not_checked`).\n\nAn unknown layout needs `mapping`; it is saved on the account (`save_mapping`, default true) and reused. `multipart/form-data` (`file`, optional `mapping` JSON text, `save_mapping`, `accept_gap`) or the JSON file shape. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The statement: .csv, .xlsx or .xls, at most 20 MB."
                  }
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "mapping": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "columns": {
                        "type": "object",
                        "properties": {
                          "date": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "description": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "debit": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "credit": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "amount": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "direction": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "balance": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          },
                          "document": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          }
                        },
                        "description": "role → the header text of that column in the file. `date`, `description` and either `amount` or `debit`/`credit` are required."
                      },
                      "date_format": {
                        "type": "string",
                        "enum": [
                          "dmy",
                          "ymd",
                          "mdy"
                        ],
                        "description": "Order of the day, month and year in text dates (default dmy: 31/10/2026). Real spreadsheet dates need no format."
                      },
                      "amount_sign": {
                        "type": "string",
                        "enum": [
                          "positive_inbound",
                          "positive_outbound"
                        ],
                        "description": "Only for a single signed `amount` column. Default: positive = abono for an account, positive = cargo (a purchase) for a credit card."
                      }
                    },
                    "required": [
                      "columns"
                    ],
                    "description": "Column mapping for an unknown format (JSON text in multipart)."
                  },
                  "save_mapping": {
                    "type": "boolean",
                    "description": "Save `mapping` on the account for the next upload. Default true."
                  },
                  "accept_gap": {
                    "type": "boolean",
                    "description": "Import even though the file’s opening balance differs from the account’s ledger balance (the account balance is restated to the bank’s)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "cartola-octubre.csv",
                "content": "RmVjaGE7RGVzY3JpcGNpw7NuOy4uLg==",
                "content_encoding": "base64",
                "accept_gap": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The import result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCartolaImportResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "import": {
                      "id": "0195f2c9-0a11-7c20-8d6f-91b7e4a3c2d1",
                      "file_name": "cartola-octubre.csv",
                      "format": "banco_de_chile",
                      "date_from": "2026-10-01",
                      "date_to": "2026-10-28",
                      "created_at": "2026-10-30T14:02:11.000Z"
                    },
                    "counts": {
                      "rows_read": 14,
                      "new": 14,
                      "duplicate": 0,
                      "rejected": 0,
                      "skipped": 1
                    },
                    "rejections": [],
                    "continuity": {
                      "status": "ok",
                      "reason": null,
                      "file_opening_minor": -400000,
                      "file_opening_on": "2026-09-30",
                      "ledger_balance_minor": -400000,
                      "gap_minor": 0,
                      "gap_from": null,
                      "gap_to": null
                    },
                    "gap_accepted": false,
                    "closing_gap_minor": 0,
                    "mapping_saved": false,
                    "account": {
                      "id": "0195f2c8-7d3e-7a10-9b4e-2a6f3c1d8e55",
                      "name": "Visa empresa",
                      "institution_name": "Banco de Chile",
                      "number_masked": "•••• 4821",
                      "type": "tarjeta_credito",
                      "currency": "CLP",
                      "source": "manual",
                      "has_feed": false,
                      "balance_clp": -1250000,
                      "balance_original": null,
                      "balance_as_of": "2026-10-28T00:00:00.000Z",
                      "balance_source": "computed",
                      "opening_balance": {
                        "amount_minor": -400000,
                        "on": "2026-09-30"
                      },
                      "opening_balance_editable": true,
                      "movement_count": 14
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/bank-accounts/{id}/cartola-mapping": {
      "delete": {
        "summary": "Forget an account's saved column mapping",
        "description": "The next upload is read automatically again (or asks for a new mapping). Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Forgotten"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/classifications": {
      "get": {
        "summary": "Classification markers for a page of documents or movements",
        "description": "For up to 200 ids of one type: classified or not, who classified it (`source`: `manual`, `rule` = marker «Regla», `ia`, `chipax`), the accounts and whether it is split. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "$ref": "#/components/schemas/FinanzasItemType"
            },
            "required": true,
            "name": "type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Comma-separated ids (at most 200)."
            },
            "required": true,
            "name": "ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The markers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasClassificationSummaries"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "state": "classified",
                        "source": "rule",
                        "rule": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                          "name": "Jardinero"
                        },
                        "accounts": [
                          {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                            "name": "Mantenimiento",
                            "path": "Gastos de administración › Mantenimiento"
                          }
                        ],
                        "split": false
                      },
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                        "state": "unclassified",
                        "source": null,
                        "rule": null,
                        "accounts": [],
                        "split": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/classifications/{type}/{id}": {
      "get": {
        "summary": "An item’s classification and its history",
        "description": "The lines of a document (`documents`) or bank movement (`movements`) and every classify / reclassify / unclassify (who, when, before, after). Classification is not double-entry: each line puts whole CLP on one account in one accrual month (`YYYY-MM`), and the lines of an item sum EXACTLY to its total (`sum_mismatch` otherwise; a remainder is never absorbed). A movement is never classified to an `ingreso` account (`movement_to_income`): income comes from documents. Rule breaks answer `422 FINANZAS_CLASSIFICATION_INVALID`, state conflicts `409 FINANZAS_CLASSIFICATION_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "documents",
                "movements"
              ],
              "description": "`documents` (any register) or `movements` (bank movements)."
            },
            "required": true,
            "name": "type",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The classification",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasClassification"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "item": {
                      "type": "document",
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a01",
                      "amount_clp": 1190000,
                      "day": "2026-10-05",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5d01",
                        "name": "Laboratorio Dental Andes Ltda"
                      },
                      "counterparty_name": "LABORATORIO DENTAL ANDES LTDA",
                      "description": null,
                      "direction": null,
                      "document_kind": "purchase",
                      "expense_document_id": null
                    },
                    "state": "classified",
                    "source": "manual",
                    "rule": null,
                    "lines": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5e01",
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                          "name": "Laboratorio",
                          "path": "Costos directos › Laboratorio",
                          "kind": "costo",
                          "archived": false
                        },
                        "amount_clp": 900000,
                        "period": "2026-10",
                        "source": "manual",
                        "rule": null,
                        "classified_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "created_at": "2026-10-30T15:20:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5e02",
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d11",
                          "name": "Insumos clínicos",
                          "path": "Costos directos › Insumos clínicos",
                          "kind": "costo",
                          "archived": false
                        },
                        "amount_clp": 290000,
                        "period": "2026-09",
                        "source": "manual",
                        "rule": null,
                        "classified_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "created_at": "2026-10-30T15:20:00.000Z"
                      }
                    ],
                    "history": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6001",
                        "action": "reclassify",
                        "actor": "user",
                        "actor_user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "rule_id": null,
                        "before": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                            "amount_clp": 1190000,
                            "period": "2026-10"
                          }
                        ],
                        "after": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                            "amount_clp": 900000,
                            "period": "2026-10"
                          },
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d11",
                            "amount_clp": 290000,
                            "period": "2026-09"
                          }
                        ],
                        "created_at": "2026-10-30T15:20:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Classify, split or reclassify a document or movement",
        "description": "Replaces the item’s lines. One account for the whole amount (`account_id` / `account_name`, optional `period`), or `lines` to split it across accounts and accrual months («Dividir»). Reclassifying is allowed and audited. A movement a gasto was created from answers `409 expense_exists` (classify the gasto); one with a live reconciliation link answers `409 movement_linked` (undo the link first). Name the account by `account_id` or in plain language with `account_name` (name or path, no case/accents); an ambiguous name answers `422 account_ambiguous` with `details.candidates` to choose from. Classification is not double-entry: each line puts whole CLP on one account in one accrual month (`YYYY-MM`), and the lines of an item sum EXACTLY to its total (`sum_mismatch` otherwise; a remainder is never absorbed). A movement is never classified to an `ingreso` account (`movement_to_income`): income comes from documents. Rule breaks answer `422 FINANZAS_CLASSIFICATION_INVALID`, state conflicts `409 FINANZAS_CLASSIFICATION_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "documents",
                "movements"
              ],
              "description": "`documents` (any register) or `movements` (bank movements)."
            },
            "required": true,
            "name": "type",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasClassifyBody"
              },
              "example": {
                "lines": [
                  {
                    "account_name": "Laboratorio",
                    "amount_clp": 900000,
                    "period": "2026-10"
                  },
                  {
                    "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d11",
                    "amount_clp": 290000,
                    "period": "2026-09"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The classification",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasClassification"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "item": {
                      "type": "document",
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a01",
                      "amount_clp": 1190000,
                      "day": "2026-10-05",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5d01",
                        "name": "Laboratorio Dental Andes Ltda"
                      },
                      "counterparty_name": "LABORATORIO DENTAL ANDES LTDA",
                      "description": null,
                      "direction": null,
                      "document_kind": "purchase",
                      "expense_document_id": null
                    },
                    "state": "classified",
                    "source": "manual",
                    "rule": null,
                    "lines": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5e01",
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                          "name": "Laboratorio",
                          "path": "Costos directos › Laboratorio",
                          "kind": "costo",
                          "archived": false
                        },
                        "amount_clp": 900000,
                        "period": "2026-10",
                        "source": "manual",
                        "rule": null,
                        "classified_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "created_at": "2026-10-30T15:20:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5e02",
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d11",
                          "name": "Insumos clínicos",
                          "path": "Costos directos › Insumos clínicos",
                          "kind": "costo",
                          "archived": false
                        },
                        "amount_clp": 290000,
                        "period": "2026-09",
                        "source": "manual",
                        "rule": null,
                        "classified_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "created_at": "2026-10-30T15:20:00.000Z"
                      }
                    ],
                    "history": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6001",
                        "action": "reclassify",
                        "actor": "user",
                        "actor_user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5f01",
                        "rule_id": null,
                        "before": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                            "amount_clp": 1190000,
                            "period": "2026-10"
                          }
                        ],
                        "after": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                            "amount_clp": 900000,
                            "period": "2026-10"
                          },
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d11",
                            "amount_clp": 290000,
                            "period": "2026-09"
                          }
                        ],
                        "created_at": "2026-10-30T15:20:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Unclassify a document or movement",
        "description": "Removes the lines (back to «sin clasificar»); audited. Idempotent. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "documents",
                "movements"
              ],
              "description": "`documents` (any register) or `movements` (bank movements)."
            },
            "required": true,
            "name": "type",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The classification, now empty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasClassification"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "item": {
                      "type": "document",
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a01",
                      "amount_clp": 1190000,
                      "day": "2026-10-05",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5d01",
                        "name": "Laboratorio Dental Andes Ltda"
                      },
                      "counterparty_name": "LABORATORIO DENTAL ANDES LTDA",
                      "description": null,
                      "direction": null,
                      "document_kind": "purchase",
                      "expense_document_id": null
                    },
                    "state": "unclassified",
                    "source": null,
                    "rule": null,
                    "lines": [],
                    "history": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/classifications/bulk": {
      "post": {
        "summary": "Classify many documents or movements to one account",
        "description": "Up to 500 items, each with its whole amount and its own month (or `period` for all). One transaction: all or nothing. Classified items are reclassified (audited); a reconciled movement refuses the batch (`409 movement_linked`). Name the account by `account_id` or in plain language with `account_name` (name or path, no case/accents); an ambiguous name answers `422 account_ambiguous` with `details.candidates` to choose from. Classification is not double-entry: each line puts whole CLP on one account in one accrual month (`YYYY-MM`), and the lines of an item sum EXACTLY to its total (`sum_mismatch` otherwise; a remainder is never absorbed). A movement is never classified to an `ingreso` account (`movement_to_income`): income comes from documents. Rule breaks answer `422 FINANZAS_CLASSIFICATION_INVALID`, state conflicts `409 FINANZAS_CLASSIFICATION_CONFLICT`, both with `details.reason`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasBulkClassifyBody"
              },
              "example": {
                "items": [
                  {
                    "type": "movement",
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01"
                  },
                  {
                    "type": "movement",
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02"
                  }
                ],
                "account_name": "Mantenimiento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were classified",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBulkClassifyResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "classified": 2,
                    "items": [
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01"
                      },
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/expenses/from-movements": {
      "post": {
        "summary": "Create gastos from many bank movements («Crear gasto»)",
        "description": "For each cargo (money out), creates one manual gasto (a document of kind `manual_expense`, paid, dated on the movement’s day, with its Contraparte) classified to the account. Up to 500 movements in ONE transaction: an abono (`movement_not_outbound`), a movement that already has a gasto (`409 expense_exists`), is reconciled (`409 movement_linked`) or is classified (`409 movement_classified`) refuses the whole batch, with `details.movement_ids`. Name the account by `account_id` or in plain language with `account_name` (name or path, no case/accents); an ambiguous name answers `422 account_ambiguous` with `details.candidates` to choose from. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasExpensesFromMovementsBody"
              },
              "example": {
                "movement_ids": [
                  "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                  "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02"
                ],
                "account_name": "Mantenimiento",
                "description": "Jardinero, sin boleta"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The gastos created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasExpensesResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "count": 2,
                    "expenses": [
                      {
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a10",
                        "amount_clp": 60000,
                        "day": "2026-10-03"
                      },
                      {
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                        "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a11",
                        "amount_clp": 60000,
                        "day": "2026-10-17"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules": {
      "get": {
        "summary": "Reglas: the classification rules",
        "description": "A rule: «for this Contraparte (and/or a text the description contains, an amount range, a movement direction or a document kind), classify to this account». Conditions are AND-combined; a rule needs a Contraparte or a text. `target: movement` classifies bank movements with no document. Rules run on new documents and movements; an item they classify carries `source: rule` (marker «Regla»). When several active rules match one item and disagree on the account, none applies. Newest first, each with its run count. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused",
                "archived",
                "all"
              ],
              "description": "Default `all`."
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRules"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rules": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                        "name": "Jardinero",
                        "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                        "target": "movement",
                        "contraparte": null,
                        "text_contains": "jardin",
                        "amount_min_clp": null,
                        "amount_max_clp": 200000,
                        "direction": "outbound",
                        "document_kind": null,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                          "name": "Mantenimiento",
                          "path": "Gastos de administración › Mantenimiento",
                          "kind": "gasto",
                          "archived": false
                        },
                        "status": "active",
                        "origin": "person",
                        "run_count": 12,
                        "last_applied_at": "2026-10-29T08:00:00.000Z",
                        "created_at": "2026-09-01T12:00:00.000Z",
                        "updated_at": "2026-10-29T08:00:00.000Z"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c02",
                        "name": null,
                        "label": "Documentos de Laboratorio Dental Andes Ltda → Costos directos › Laboratorio",
                        "target": "document",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5d01",
                          "name": "Laboratorio Dental Andes Ltda",
                          "rut": "76.123.456-0"
                        },
                        "text_contains": null,
                        "amount_min_clp": null,
                        "amount_max_clp": null,
                        "direction": null,
                        "document_kind": null,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d10",
                          "name": "Laboratorio",
                          "path": "Costos directos › Laboratorio",
                          "kind": "costo",
                          "archived": false
                        },
                        "status": "active",
                        "origin": "person",
                        "run_count": 31,
                        "last_applied_at": "2026-10-29T08:00:00.000Z",
                        "created_at": "2026-09-01T12:00:00.000Z",
                        "updated_at": "2026-10-29T08:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a rule",
        "description": "A rule: «for this Contraparte (and/or a text the description contains, an amount range, a movement direction or a document kind), classify to this account». Conditions are AND-combined; a rule needs a Contraparte or a text. `target: movement` classifies bank movements with no document. Rules run on new documents and movements; an item they classify carries `source: rule` (marker «Regla»). When several active rules match one item and disagree on the account, none applies. Name the Contraparte by `contraparte_id`, `contraparte_rut` or `contraparte_name`, and the account by `account_id` or `account_name`, so an agent can fill it from plain language. `from_item` («Siempre clasificar así») prefills from a classified item. It does not touch existing items: preview with `GET /finanzas/rules/{id}/pending`, then `POST .../apply`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasRuleCreateBody"
              },
              "example": {
                "name": "Jardinero",
                "target": "movement",
                "text_contains": "jardin",
                "direction": "outbound",
                "amount_max_clp": 200000,
                "account_name": "Mantenimiento"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 200000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "active",
                    "origin": "person",
                    "run_count": 0,
                    "last_applied_at": null,
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/rules/{id}": {
      "get": {
        "summary": "A rule",
        "description": "A rule: «for this Contraparte (and/or a text the description contains, an amount range, a movement direction or a document kind), classify to this account». Conditions are AND-combined; a rule needs a Contraparte or a text. `target: movement` classifies bank movements with no document. Rules run on new documents and movements; an item they classify carries `source: rule` (marker «Regla»). When several active rules match one item and disagree on the account, none applies. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 200000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "active",
                    "origin": "person",
                    "run_count": 12,
                    "last_applied_at": "2026-10-29T08:00:00.000Z",
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a rule",
        "description": "Change any condition, the target or the account (`null` clears a condition). Items already classified keep their lines. An archived rule answers `409 rule_archived`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasRuleUpdateBody"
              },
              "example": {
                "amount_max_clp": 250000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» hasta $250.000 → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 250000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "active",
                    "origin": "person",
                    "run_count": 12,
                    "last_applied_at": "2026-10-29T08:00:00.000Z",
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules/{id}/pause": {
      "post": {
        "summary": "Pause a rule",
        "description": "Stops it running on new items; idempotent. An archived rule cannot be paused or resumed (`409 rule_archived`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 200000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "paused",
                    "origin": "person",
                    "run_count": 12,
                    "last_applied_at": "2026-10-29T08:00:00.000Z",
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules/{id}/resume": {
      "post": {
        "summary": "Resume a paused rule",
        "description": "Runs on new items again; idempotent. An archived rule cannot be paused or resumed (`409 rule_archived`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 200000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "active",
                    "origin": "person",
                    "run_count": 12,
                    "last_applied_at": "2026-10-29T08:00:00.000Z",
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules/{id}/archive": {
      "post": {
        "summary": "Archive a rule",
        "description": "Retires it for good (its past classifications stay); idempotent. An archived rule cannot be paused or resumed (`409 rule_archived`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRule"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "name": "Jardinero",
                    "label": "Movimientos (cargos) que contienen «jardin» → Gastos de administración › Mantenimiento",
                    "target": "movement",
                    "contraparte": null,
                    "text_contains": "jardin",
                    "amount_min_clp": null,
                    "amount_max_clp": 200000,
                    "direction": "outbound",
                    "document_kind": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d12",
                      "name": "Mantenimiento",
                      "path": "Gastos de administración › Mantenimiento",
                      "kind": "gasto",
                      "archived": false
                    },
                    "status": "archived",
                    "origin": "person",
                    "run_count": 12,
                    "last_applied_at": "2026-10-29T08:00:00.000Z",
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-29T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules/{id}/pending": {
      "get": {
        "summary": "«Aplicar a pendientes»: preview",
        "description": "How many UNCLASSIFIED items the rule would classify now, their total and up to 10 of them. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRulePending"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rule_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "count": 4,
                    "total_clp": 240000,
                    "sample": [
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "day": "2026-10-03",
                        "amount_clp": 60000,
                        "counterparty_name": "Juan Pérez",
                        "description": "Transferencia jardin octubre"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/rules/{id}/apply": {
      "post": {
        "summary": "«Aplicar a pendientes»: apply a rule to unclassified items",
        "description": "Classifies every unclassified item the rule matches (never a classified one) with `source: rule`, and adds them to its run count. An archived rule answers `409 rule_archived`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "How many it classified",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRuleApplyResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rule_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5c01",
                    "applied": 4
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/movements/{id}": {
      "get": {
        "summary": "A movement's reconciliation: links, payer, commission",
        "description": "What explains a bank movement: its Finanzas links (document, mirrored payment, own-account transfer, third party, cash deposit), each with its amount and actor (`person` Manual, `rule` Regla, `match` Coincidencia, `ai` IA), undone ones included (`undone_at`); the clinic links that predate Finanzas (`legacy_links`: settlements and payments credited from a comprobante); a Mercado Pago movement's payer (name, e-mail, and the patient when one matches); and the commission a payment processor kept on a payout, with its plan-de-cuentas account. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The reconciliation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMovementReconciliation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "movement": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                      "date": "2026-10-29",
                      "direction": "outbound",
                      "amount_clp": 1190000,
                      "currency": "CLP",
                      "original": null,
                      "description": "TRANSF A PROVEEDOR EJEMPLO SPA FACT 4521",
                      "counterparty_name": "Proveedor Ejemplo SpA",
                      "counterparty_rut_formatted": "76.123.456-7",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                        "name": "Proveedor Ejemplo SpA",
                        "rut_formatted": "76.123.456-7"
                      },
                      "bank_account": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "name": "Cuenta corriente",
                        "institution_name": "Banco de Chile",
                        "number_masked": "****4821",
                        "currency": "CLP"
                      },
                      "state": "reconciled",
                      "assigned_clp": 1190000,
                      "pending_clp": 0,
                      "resolved_by": "match",
                      "source": "bank_feed",
                      "operation_id": null,
                      "payer": null,
                      "comment_count": 0,
                      "ads_document_id": null,
                      "card_processor": null,
                      "generic_label": null,
                      "created_at": "2026-10-30T08:00:00.000Z"
                    },
                    "money_mirrored": false,
                    "payer": null,
                    "links": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e01",
                        "target": "document",
                        "amount_clp": 1190000,
                        "actor": "match",
                        "actor_user_id": null,
                        "reasons": [
                          "folio_and_amount"
                        ],
                        "note": null,
                        "created_at": "2026-10-30T08:05:00.000Z",
                        "undone_at": null,
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                        "document": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                          "kind": "purchase",
                          "dte_type": 33,
                          "folio": "4521",
                          "issue_date": "2026-10-01",
                          "total_clp": 1190000,
                          "counterparty_name": "Proveedor Ejemplo SpA"
                        },
                        "payment": null,
                        "paired_movement": null,
                        "cash_day": null,
                        "contraparte": null,
                        "payout": null,
                        "undoable": true
                      }
                    ],
                    "legacy_links": [],
                    "commission": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/movements/{id}/candidates": {
      "get": {
        "summary": "Candidates to reconcile a movement («Conciliar»)",
        "description": "Open documents (right direction, with what each still owes), payments (a mirrored clinic payment, or a Caja payment for a native clinic), the other half of an own-account transfer (same amount, another own account, ±5 days) and days of «efectivo por depositar» (incoming movements). Without `q`, documents are prefiltered by folio in the description, Contraparte (including its linked RUTs and accounts) and amount; with `q`, a free search. `pending_clp` is what remains to assign. Clinic money authority: while a clinic is mirrored from its clinic system, an incoming movement only links to mirrored payments, as evidence; it never creates a payment or income (a sale-document link answers 400). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free search (folio, counterparty, patient, description). Without it the candidates are prefiltered by Contraparte, folio and amount."
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The candidates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMovementCandidates"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "pending_clp": 1190000,
                    "money_mirrored": false,
                    "documents": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                        "kind": "purchase",
                        "dte_type": 33,
                        "folio": "4521",
                        "issue_date": "2026-10-01",
                        "due_date": "2026-10-31",
                        "total_clp": 1190000,
                        "outstanding_clp": 1190000,
                        "counterparty_name": "Proveedor Ejemplo SpA",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                          "name": "Proveedor Ejemplo SpA"
                        },
                        "signal": "folio",
                        "suggested_amount_clp": 1190000
                      }
                    ],
                    "payments": [],
                    "transfers": [],
                    "cash_days": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/documents/{id}": {
      "get": {
        "summary": "A document's payments and candidates («Asignar pago»)",
        "description": "A document with what is paid (`paid_clp`), what remains (`outstanding_clp`) and its derived `payment_state` (`por_pagar`, `parcial`, `pagado`; for a sale read «por cobrar»), its links, and the open movements that could pay it (prefiltered by folio, Contraparte and amount, or by `q`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free search (folio, counterparty, patient, description). Without it the candidates are prefiltered by Contraparte, folio and amount."
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The document reconciliation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDocumentReconciliation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "document": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                      "kind": "purchase",
                      "dte_type": 33,
                      "folio": "4521",
                      "issue_date": "2026-10-01",
                      "due_date": "2026-10-31",
                      "counterparty_name": "Proveedor Ejemplo SpA",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                        "name": "Proveedor Ejemplo SpA"
                      },
                      "total_clp": 1190000,
                      "paid_clp": 700000,
                      "outstanding_clp": 490000,
                      "payment_state": "parcial"
                    },
                    "links": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e01",
                        "target": "document",
                        "amount_clp": 700000,
                        "actor": "match",
                        "actor_user_id": null,
                        "reasons": [
                          "folio_and_amount"
                        ],
                        "note": null,
                        "created_at": "2026-10-30T08:05:00.000Z",
                        "undone_at": null,
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                        "document": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                          "kind": "purchase",
                          "dte_type": 33,
                          "folio": "4521",
                          "issue_date": "2026-10-01",
                          "total_clp": 1190000,
                          "counterparty_name": "Proveedor Ejemplo SpA"
                        },
                        "payment": null,
                        "paired_movement": null,
                        "cash_day": null,
                        "contraparte": null,
                        "payout": null,
                        "undoable": true
                      }
                    ],
                    "candidates": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a11",
                        "day": "2026-10-30",
                        "direction": "outbound",
                        "amount_clp": 490000,
                        "pending_clp": 490000,
                        "description": "TRANSF A PROVEEDOR EJEMPLO SPA",
                        "counterparty_name": "Proveedor Ejemplo SpA",
                        "bank_account_name": "Cuenta corriente",
                        "signal": "contraparte",
                        "suggested_amount_clp": 490000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/links": {
      "post": {
        "summary": "Link a movement (Conciliar)",
        "description": "Links one movement to one or more targets, atomically: documents (partial amounts allowed), a payment, the other half of an own-account transfer («Traspaso entre cuentas propias»: both close, neither counts in the result), a third party («Cuenta de terceros», excluded from the result) or days of «efectivo por depositar». `amount_clp` omitted = what remains on both sides; the amounts may not exceed what remains of the movement nor of each document (400). A Caja payment is a clinic settlement and goes alone. The document payment state is re-derived. «Clasificar sin documento» is not a link: it is the movement's classification (`PUT /finanzas/classifications/{type}/{id}`, type `movements`). Returns the movement's reconciliation. Audited. Clinic money authority: while a clinic is mirrored from its clinic system, an incoming movement only links to mirrored payments, as evidence; it never creates a payment or income (a sale-document link answers 400). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "movement_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "links": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "target": {
                              "type": "string",
                              "enum": [
                                "document"
                              ]
                            },
                            "document_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "amount_clp": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991,
                              "description": "Whole CLP pesos, > 0."
                            }
                          },
                          "required": [
                            "target",
                            "document_id"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "target": {
                              "type": "string",
                              "enum": [
                                "payment"
                              ]
                            },
                            "payment_id": {
                              "type": "string",
                              "format": "uuid",
                              "description": "A payment from `candidates.payments`: a mirrored clinic payment (the movement becomes its evidence) or a Caja payment (a clinic settlement, linked alone)."
                            },
                            "amount_clp": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991,
                              "description": "Whole CLP pesos, > 0."
                            }
                          },
                          "required": [
                            "target",
                            "payment_id"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "target": {
                              "type": "string",
                              "enum": [
                                "transfer"
                              ]
                            },
                            "paired_movement_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid",
                              "description": "«Traspaso entre cuentas propias»: the opposite movement on another own account, same amount. Omit it when the other half is not in Vitrina (an own account that is not connected or registered yet): allowed only when the bank names the tenant's own RUT or one of its own accounts as the other side (400 `VALIDATION_ERROR` otherwise); the whole movement is the traspaso."
                            }
                          },
                          "required": [
                            "target"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "target": {
                              "type": "string",
                              "enum": [
                                "third_party"
                              ]
                            },
                            "amount_clp": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991,
                              "description": "Whole CLP pesos, > 0."
                            },
                            "contraparte_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            }
                          },
                          "required": [
                            "target"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "target": {
                              "type": "string",
                              "enum": [
                                "cash_deposit"
                              ]
                            },
                            "cash_day_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "amount_clp": {
                              "type": "integer",
                              "exclusiveMinimum": 0,
                              "maximum": 9007199254740991,
                              "description": "Whole CLP pesos, > 0."
                            }
                          },
                          "required": [
                            "target",
                            "cash_day_id"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 50,
                    "description": "One or more targets. `amount_clp` omitted = what remains on both sides. The amounts may not exceed what remains of the movement."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "movement_id",
                  "links"
                ]
              },
              "example": {
                "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                "links": [
                  {
                    "target": "document",
                    "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "amount_clp": 700000
                  },
                  {
                    "target": "third_party",
                    "amount_clp": 490000
                  }
                ],
                "note": "Abono parcial a la factura 4521; el resto es de un socio."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The reconciliation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMovementReconciliation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "movement": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                      "date": "2026-10-29",
                      "direction": "outbound",
                      "amount_clp": 1190000,
                      "currency": "CLP",
                      "original": null,
                      "description": "TRANSF A PROVEEDOR EJEMPLO SPA FACT 4521",
                      "counterparty_name": "Proveedor Ejemplo SpA",
                      "counterparty_rut_formatted": "76.123.456-7",
                      "contraparte": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                        "name": "Proveedor Ejemplo SpA",
                        "rut_formatted": "76.123.456-7"
                      },
                      "bank_account": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                        "name": "Cuenta corriente",
                        "institution_name": "Banco de Chile",
                        "number_masked": "****4821",
                        "currency": "CLP"
                      },
                      "state": "reconciled",
                      "assigned_clp": 1190000,
                      "pending_clp": 0,
                      "resolved_by": "match",
                      "source": "bank_feed",
                      "operation_id": null,
                      "payer": null,
                      "comment_count": 0,
                      "ads_document_id": null,
                      "card_processor": null,
                      "generic_label": null,
                      "created_at": "2026-10-30T08:00:00.000Z"
                    },
                    "money_mirrored": false,
                    "payer": null,
                    "links": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e01",
                        "target": "document",
                        "amount_clp": 700000,
                        "actor": "person",
                        "actor_user_id": null,
                        "reasons": [
                          "linked_by_staff"
                        ],
                        "note": null,
                        "created_at": "2026-10-30T08:05:00.000Z",
                        "undone_at": null,
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                        "document": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                          "kind": "purchase",
                          "dte_type": 33,
                          "folio": "4521",
                          "issue_date": "2026-10-01",
                          "total_clp": 1190000,
                          "counterparty_name": "Proveedor Ejemplo SpA"
                        },
                        "payment": null,
                        "paired_movement": null,
                        "cash_day": null,
                        "contraparte": null,
                        "payout": null,
                        "undoable": true
                      }
                    ],
                    "legacy_links": [],
                    "commission": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/links/{id}/undo": {
      "post": {
        "summary": "Undo a link (Deshacer)",
        "description": "Undoes a link: both sides get the amount back and the document state is re-derived. The link stays with `undone_at` (audited), and the engine never re-applies that pair. A clinic settlement (`settlement:<id>` from `legacy_links`) runs the clinic «Deshacer»; a payment credited from the bank's comprobante answers 409. 409 when already undone. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:(?:settlement|payment):)?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
              "description": "A Finanzas link id, or a pre-Finanzas clinic link as listed in `legacy_links` (`settlement:<uuid>`)."
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Undone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "movement_id": {
                          "type": "string"
                        },
                        "document_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "movement_id",
                        "document_id"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                    "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/run": {
      "post": {
        "summary": "Run the reconciliation engine for a period",
        "description": "Runs the deterministic engine over the open movements of a period (at most one year): gastos created from a movement («Crear gasto»), own-account transfers, cash deposits against «efectivo por depositar», mirrored clinic payments by the payer the bank or Mercado Pago names, and documents by folio, RUT (with the Contraparte's linked RUTs and accounts), amount and date window. It applies only a unique match (actor `match`, «Coincidencia») and never re-applies a pair a person undid; the rest stays for «Por resolver». It also runs on its own after every bank and SII sync. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "from",
                  "to"
                ]
              },
              "example": {
                "from": "2026-10-01",
                "to": "2026-10-31"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The run",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasEngineResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "skipped": false,
                    "from": "2026-10-01",
                    "to": "2026-10-31",
                    "movements_seen": 214,
                    "applied": {
                      "documents": 37,
                      "transfers": 4,
                      "cash_deposits": 6,
                      "payments": 52,
                      "expenses": 3
                    },
                    "ambiguous": 9
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/reconciliation/cash-pending": {
      "get": {
        "summary": "Efectivo por depositar",
        "description": "Per business day, the cash not yet in a bank: from Caja del día (the day's cash payments) when the workspace records payments in Vitrina, else the day's boletas minus what payment processors settled for that day. `remaining_clp` drops as deposits link to it. `only_open` (default true) hides deposited days. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day, inclusive. Optional: the list is a standing one; without it, 365 days before `to`."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day, inclusive. Optional: today in Chile."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "Only days with cash still to deposit (default true)."
            },
            "required": false,
            "name": "only_open",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The cash days",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashPending"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                        "day": "2026-10-28",
                        "source": "boletas",
                        "amount_clp": 400000,
                        "deposited_clp": 0,
                        "remaining_clp": 400000,
                        "detail": {
                          "boletas_clp": 2406000,
                          "processors_clp": 2006000,
                          "transfers_clp": 0
                        }
                      }
                    ],
                    "total_remaining_clp": 400000,
                    "pending": {
                      "amount_clp": 400000,
                      "window_days": 60,
                      "expected_until": "2026-11-06"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/reconciliation/commissions": {
      "get": {
        "summary": "Payout commission lines",
        "description": "What Mercado Pago or a card acquirer (Transbank) kept on each payout of the period: Mercado Pago's own fee when it reports one, else a card batch's gross minus its deposit. Each line is classified to the plan de cuentas' payment-processor commission account (`account`, null when the plan has none). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day, inclusive."
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day, inclusive."
            },
            "required": true,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The commission lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCommissions"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a12",
                        "day": "2026-10-28",
                        "amount_clp": 71400,
                        "source": "mercado_pago",
                        "bank_account_name": "Cuenta Mercado Pago",
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b12",
                          "name": "Comisiones de pasarelas de pago"
                        }
                      }
                    ],
                    "total_clp": 71400
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases": {
      "get": {
        "summary": "The «Por resolver» inbox: open cases, biggest first",
        "description": "A case is the open money of one Contraparte that Vitrina could not resolve on its own: bank movements nothing explains, cost documents with no account, and money out classified with no document («Falta documento»). Cases are recomputed after every bank, Mercado Pago and SII sync and before every read; only cases whose inputs changed are rewritten. Sorted by the money at stake (`order=needs_person`: the cases a person must answer first). `kind`, `month` (`YYYY-MM`: a case belongs to ONE month, its oldest item's) and `period` (deprecated: `recent` / `history`) filter; `total` and `amount_at_stake_clp` cover every open case of that filter, `counts_by_month` lists every month with open cases (the month filter's options; months «Cerrado antes de Vitrina» hold none), `counts` splits them (needs_person / ready) and `waiting` lists the payments «esperando factura» (no case yet). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "reconcile",
                "transfer",
                "classify",
                "missing_document",
                "unknown",
                "ads_charge",
                "merge"
              ],
              "description": "reconcile = movements ↔ documents; transfer = between own accounts; classify = to an account; missing_document = «Falta documento» (money out classified with no document); unknown = no proposal yet; ads_charge = a Meta / Google card charge Vitrina could not confirm («Cargo de publicidad sin confirmar» / «Sin conexión de publicidad», Finanzas 30): the proposal links each movement to its own ads document; merge = a «unir» Caso (Finanzas 58): two Contrapartes with similar names and no shared RUT, proposed to be merged (`merge` holds the pair; it has no movements or documents and moves no money)."
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Page size (default 50, max 200)."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 100000
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "history",
                "all"
              ],
              "description": "`recent` = cases touching this month or last month (from `since`); `history` = only older ones («Historial»); `all` (default)."
            },
            "required": false,
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Only the cases of that month, `YYYY-MM`. A case belongs to ONE month: the one of its oldest item (a movement, or a document to classify). `total` and `amount_at_stake_clp` follow the filter; `counts_by_month` does not."
            },
            "required": false,
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "amount",
                "needs_person"
              ],
              "description": "`amount` (default): biggest at stake first. `needs_person`: the cases a person must answer (not `ready`) first, then by amount."
            },
            "required": false,
            "name": "order",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The open cases",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCaseList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                        "kind": "reconcile",
                        "title": "Laboratorio Dental Andes Ltda",
                        "summary": "1 cargo ($775.290) vs 2 facturas",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                          "name": "Laboratorio Dental Andes Ltda",
                          "rut": "76123456-7"
                        },
                        "amount_at_stake_clp": 775290,
                        "item_count": 1,
                        "movement_count": 1,
                        "document_count": 2,
                        "ready": true,
                        "opened_at": "2026-10-22T09:00:00.000Z",
                        "latest_day": "2026-10-21",
                        "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e"
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                        "kind": "missing_document",
                        "title": "Maestro jardinero",
                        "summary": "1 egreso sin documento ($80.000)",
                        "contraparte": null,
                        "amount_at_stake_clp": 80000,
                        "item_count": 1,
                        "movement_count": 1,
                        "document_count": 0,
                        "ready": true,
                        "opened_at": "2026-10-23T09:00:00.000Z",
                        "latest_day": "2026-10-20",
                        "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e"
                      }
                    ],
                    "total": 2,
                    "amount_at_stake_clp": 855290,
                    "since": "2026-09-01",
                    "counts_by_month": [
                      {
                        "month": "2026-10",
                        "open": 2,
                        "amount_clp": 855290
                      }
                    ],
                    "counts": {
                      "recent": 2,
                      "history": 0,
                      "needs_person": 0,
                      "ready": 2
                    },
                    "waiting": {
                      "count": 0,
                      "amount_clp": 0,
                      "items": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/count": {
      "get": {
        "summary": "«Te necesita»: how many cases are open",
        "description": "The one count of open work (Finanzas 47): open cases, the money at stake and the count per kind. Resumen, Por resolver, Movimientos, Cierre de mes and the weekly digest show this same number for the same scope. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Only the cases of that month, `YYYY-MM` (the month of the case’s oldest item): the same count and amount as «Por resolver» with `month`, and as the month’s «Cierre de mes» checklist."
            },
            "required": false,
            "name": "month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The open-case count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCaseCount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "open": 2,
                    "amount_at_stake_clp": 855290,
                    "by_kind": {
                      "reconcile": 1,
                      "transfer": 0,
                      "classify": 0,
                      "missing_document": 1,
                      "unknown": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/{id}": {
      "get": {
        "summary": "One case with its proposal",
        "description": "The case’s movements (`role` open | missing_document), documents (`role` unclassified | candidate: what the proposal pays plus a few more of the Contraparte to swap in), days of «efectivo por depositar», the proposed actions (link | classify | accept_without_document, each with stable `reasons`), the Spanish `why`, and the items with no proposal (`unresolved`; `ready` is false then). A resolved case still reads (`status`, `resolution` confirmed | cleared). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The case",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCase"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                    "key": "contraparte:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                    "kind": "reconcile",
                    "status": "open",
                    "resolution": null,
                    "resolved_at": null,
                    "contraparte": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                      "name": "Laboratorio Dental Andes Ltda",
                      "rut": "76123456-7"
                    },
                    "title": "Laboratorio Dental Andes Ltda",
                    "summary": "1 cargo ($775.290) vs 2 facturas",
                    "amount_at_stake_clp": 775290,
                    "item_count": 1,
                    "ready": true,
                    "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e",
                    "movements": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                        "day": "2026-10-21",
                        "direction": "outbound",
                        "amount_clp": 775290,
                        "pending_clp": 775290,
                        "description": "TRANSF A LABORATORIO DENTAL ANDES",
                        "counterparty_name": "Laboratorio Dental Andes Ltda",
                        "bank_account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a6c",
                          "name": "Cuenta corriente"
                        },
                        "role": "open",
                        "classification": null
                      }
                    ],
                    "documents": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                        "kind": "purchase",
                        "dte_type": 33,
                        "folio": "18204",
                        "issue_date": "2026-09-12",
                        "due_date": "2026-10-12",
                        "total_clp": 412690,
                        "gross_clp": 412690,
                        "outstanding_clp": 412690,
                        "counterparty_name": "Laboratorio Dental Andes Ltda",
                        "role": "candidate",
                        "classified": true
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                        "kind": "purchase",
                        "dte_type": 33,
                        "folio": "18377",
                        "issue_date": "2026-09-26",
                        "due_date": "2026-10-26",
                        "total_clp": 362600,
                        "gross_clp": 362600,
                        "outstanding_clp": 362600,
                        "counterparty_name": "Laboratorio Dental Andes Ltda",
                        "role": "candidate",
                        "classified": true
                      }
                    ],
                    "cash_days": [],
                    "proposal": {
                      "actions": [
                        {
                          "type": "link",
                          "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                          "links": [
                            {
                              "target": "document",
                              "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                              "amount_clp": 412690
                            },
                            {
                              "target": "document",
                              "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                              "amount_clp": 362600
                            }
                          ],
                          "replace_classification": false,
                          "reasons": [
                            "sum_of_documents"
                          ]
                        }
                      ],
                      "why": [
                        "La suma de 2 facturas es exacta: $775.290, el cargo del 21 oct"
                      ]
                    },
                    "unresolved": [],
                    "opened_at": "2026-10-22T09:00:00.000Z",
                    "computed_at": "2026-10-22T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/confirm-bulk": {
      "post": {
        "summary": "«Confirmar en bloque»: several ready cases at once",
        "description": "Confirms each case’s own proposal (every case must be `ready`), with the fingerprint the screen read it with. Each case is its own transaction: a changed, resolved, incomplete or refused case stays open and is listed in `failed` with its reason; the rest are confirmed. At most 100 per call. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasCasesBulkConfirmBody"
              },
              "example": {
                "cases": [
                  {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                    "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed and refused cases",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCasesBulkConfirmResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "confirmed": [
                      {
                        "case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                        "applied": {
                          "links": 1,
                          "classifications": 0,
                          "accepted": 0
                        }
                      }
                    ],
                    "failed": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/{id}/confirm": {
      "post": {
        "summary": "Confirm a case’s proposal («Resolver»)",
        "description": "Applies the case’s own proposal, which must cover every item (`ready`; else 422 `FINANZAS_CASE_INVALID` `proposal_incomplete` with `details.items`). ONE transaction: every link (the Conciliar rules: amounts, directions, clinic money authority), classification (the classification rules) and «Aceptar sin documento» lands, or none does; a refused step answers with that step’s own error (400 `VALIDATION_ERROR`, 409 `CONFLICT`, 422 `FINANZAS_CLASSIFICATION_INVALID` …). Links are written with actor `person` (the caller), undoable from the movement. Returns the next case (`next_case_id`) for J/K triage and the phone’s «siguiente». `409 FINANZAS_CASE_CHANGED` (`details.reason` `fingerprint_mismatch` | `resolved`, `details.case` = the case as it is now, or null): nothing was written; show the fresh case and confirm again. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fingerprint": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "description": "The `fingerprint` of the case as it was read. A case that changed since answers 409 `FINANZAS_CASE_CHANGED` with the fresh case; nothing is written."
                  }
                },
                "required": [
                  "fingerprint"
                ],
                "additionalProperties": false
              },
              "example": {
                "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCaseConfirmResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                    "applied": {
                      "links": 2,
                      "classifications": 0,
                      "accepted": 0
                    },
                    "remaining_case": null,
                    "next_case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                    "descriptors": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/{id}/confirm-edited": {
      "post": {
        "summary": "Confirm an edited proposal",
        "description": "The person’s version of the proposal: swap a document, change the account, accept a cargo without document, or confirm only part of the case (what is left stays open as `remaining_case`). Every movement or classified item must belong to the case (422 `item_not_in_case`; one action per item, `duplicate_item`); a link may target any document. `replace_classification` (a «Falta documento» whose document arrived) removes the classification before linking. ONE transaction: every link (the Conciliar rules: amounts, directions, clinic money authority), classification (the classification rules) and «Aceptar sin documento» lands, or none does; a refused step answers with that step’s own error (400 `VALIDATION_ERROR`, 409 `CONFLICT`, 422 `FINANZAS_CLASSIFICATION_INVALID` …). Links are written with actor `person` (the caller), undoable from the movement. Returns the next case (`next_case_id`) for J/K triage and the phone’s «siguiente». `409 FINANZAS_CASE_CHANGED` (`details.reason` `fingerprint_mismatch` | `resolved`, `details.case` = the case as it is now, or null): nothing was written; show the fresh case and confirm again. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fingerprint": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "description": "Optional: the fingerprint the edit was made on (409 when the case changed)."
                  },
                  "actions": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "link"
                              ]
                            },
                            "movement_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "links": {
                              "type": "array",
                              "items": {
                                "oneOf": [
                                  {
                                    "type": "object",
                                    "properties": {
                                      "target": {
                                        "type": "string",
                                        "enum": [
                                          "document"
                                        ]
                                      },
                                      "document_id": {
                                        "type": "string",
                                        "format": "uuid"
                                      },
                                      "amount_clp": {
                                        "type": "integer",
                                        "exclusiveMinimum": 0,
                                        "maximum": 9007199254740991,
                                        "description": "Whole CLP pesos, > 0."
                                      }
                                    },
                                    "required": [
                                      "target",
                                      "document_id"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "target": {
                                        "type": "string",
                                        "enum": [
                                          "payment"
                                        ]
                                      },
                                      "payment_id": {
                                        "type": "string",
                                        "format": "uuid",
                                        "description": "A payment from `candidates.payments`: a mirrored clinic payment (the movement becomes its evidence) or a Caja payment (a clinic settlement, linked alone)."
                                      },
                                      "amount_clp": {
                                        "type": "integer",
                                        "exclusiveMinimum": 0,
                                        "maximum": 9007199254740991,
                                        "description": "Whole CLP pesos, > 0."
                                      }
                                    },
                                    "required": [
                                      "target",
                                      "payment_id"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "target": {
                                        "type": "string",
                                        "enum": [
                                          "transfer"
                                        ]
                                      },
                                      "paired_movement_id": {
                                        "type": [
                                          "string",
                                          "null"
                                        ],
                                        "format": "uuid",
                                        "description": "«Traspaso entre cuentas propias»: the opposite movement on another own account, same amount. Omit it when the other half is not in Vitrina (an own account that is not connected or registered yet): allowed only when the bank names the tenant's own RUT or one of its own accounts as the other side (400 `VALIDATION_ERROR` otherwise); the whole movement is the traspaso."
                                      }
                                    },
                                    "required": [
                                      "target"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "target": {
                                        "type": "string",
                                        "enum": [
                                          "third_party"
                                        ]
                                      },
                                      "amount_clp": {
                                        "type": "integer",
                                        "exclusiveMinimum": 0,
                                        "maximum": 9007199254740991,
                                        "description": "Whole CLP pesos, > 0."
                                      },
                                      "contraparte_id": {
                                        "type": [
                                          "string",
                                          "null"
                                        ],
                                        "format": "uuid"
                                      }
                                    },
                                    "required": [
                                      "target"
                                    ]
                                  },
                                  {
                                    "type": "object",
                                    "properties": {
                                      "target": {
                                        "type": "string",
                                        "enum": [
                                          "cash_deposit"
                                        ]
                                      },
                                      "cash_day_id": {
                                        "type": "string",
                                        "format": "uuid"
                                      },
                                      "amount_clp": {
                                        "type": "integer",
                                        "exclusiveMinimum": 0,
                                        "maximum": 9007199254740991,
                                        "description": "Whole CLP pesos, > 0."
                                      }
                                    },
                                    "required": [
                                      "target",
                                      "cash_day_id"
                                    ]
                                  }
                                ]
                              },
                              "minItems": 1,
                              "maxItems": 50
                            },
                            "replace_classification": {
                              "type": "boolean",
                              "description": "«Falta documento» whose document arrived: remove the movement’s classification, then link it (the case’s missing-document movements only)."
                            }
                          },
                          "required": [
                            "type",
                            "movement_id",
                            "links"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "classify"
                              ]
                            },
                            "item": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "document",
                                    "movement"
                                  ]
                                },
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                }
                              },
                              "required": [
                                "type",
                                "id"
                              ],
                              "additionalProperties": false
                            },
                            "account_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "account_name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200,
                              "description": "Plain language: the account’s name or path."
                            },
                            "period": {
                              "type": "string",
                              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
                            }
                          },
                          "required": [
                            "type",
                            "item"
                          ],
                          "additionalProperties": false
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "accept_without_document"
                              ]
                            },
                            "movement_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "maxLength": 500
                            }
                          },
                          "required": [
                            "type",
                            "movement_id"
                          ],
                          "additionalProperties": false
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 200,
                    "description": "The edited proposal: every action applies in ONE transaction (all or nothing). Movements and documents must belong to the case (a link may target any document). Items left out stay open."
                  }
                },
                "required": [
                  "actions"
                ],
                "additionalProperties": false
              },
              "example": {
                "fingerprint": "9c1e4b7a2f0d5c8e1b3a6f9d2c5e8b1a4d7f0c3e",
                "actions": [
                  {
                    "type": "link",
                    "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                    "links": [
                      {
                        "target": "document",
                        "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02"
                      }
                    ]
                  },
                  {
                    "type": "classify",
                    "item": {
                      "type": "document",
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01"
                    },
                    "account_name": "Laboratorio"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCaseConfirmResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                    "applied": {
                      "links": 1,
                      "classifications": 1,
                      "accepted": 0
                    },
                    "remaining_case": null,
                    "next_case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                    "descriptors": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/{id}/dismiss": {
      "post": {
        "summary": "Dismiss a «unir» case («No son la misma»)",
        "description": "Finanzas 58, `merge` cases only (422 `not_a_merge_case` otherwise): the two Contrapartes are not the same. The pair is never proposed or merged again and the case closes. To merge them instead, use the confirm route. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCaseDismissResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                    "next_case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cases/no-document/{movement_id}": {
      "delete": {
        "summary": "Undo «Aceptar sin documento»",
        "description": "The cargo asks for its document again: its «Falta documento» case reopens (`case_id`). Its classification is untouched. 404 when it was never accepted. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "movement_id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Undone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasNoDocumentUndo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "movement_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                    "case_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/automatic-actions": {
      "get": {
        "summary": "Hecho por Vitrina: what Vitrina applied on its own in a month",
        "description": "Every automatic action whose item falls in the month (the movement day, the document's issue date), grouped by Contraparte (own-account transfers, cash deposits and patient payments have their own group), biggest total first. An action is a reconciliation link by the engine (`actor` `match` Coincidencia, `rule` Regla, `ai` IA) or a classification by a rule or the IA that is still current. Undone actions and links a person caused («Crear gasto») are left out. Each group lists at most 100 items (`items_truncated`); pass `group` for all of one group. `rules_created` lists the rules Vitrina created this month from repeated confirmations. `unreviewed_count` is what the month close reports. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM` (by the item day)."
            },
            "required": true,
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^(?:contraparte:[0-9a-f-]{36}|own_transfers|cash_deposits|clinic_payments|none)$",
              "description": "A group key as listed: `contraparte:<uuid>`, `own_transfers`, `cash_deposits`, `clinic_payments` or `none`."
            },
            "required": false,
            "name": "group",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "all",
                "unreviewed"
              ],
              "description": "unreviewed = only what nobody marked reviewed (default all)."
            },
            "required": false,
            "name": "reviewed",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The feed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAutomaticActions"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "from": "2026-10-01",
                    "to": "2026-10-31",
                    "count": 2,
                    "total_clp": 1640000,
                    "unreviewed_count": 1,
                    "by_actor": {
                      "rule": 1,
                      "match": 1,
                      "ai": 0
                    },
                    "rules_created": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                        "label": "Documentos (compras) de Proveedor Ejemplo SpA → Costos directos › Laboratorio",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                          "name": "Proveedor Ejemplo SpA"
                        },
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                          "name": "Laboratorio",
                          "path": "Costos directos › Laboratorio"
                        },
                        "status": "active",
                        "created_at": "2026-10-04T15:30:00.000Z"
                      }
                    ],
                    "groups": [
                      {
                        "key": "contraparte:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                        "contraparte": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20",
                          "name": "Proveedor Ejemplo SpA"
                        },
                        "name": "Proveedor Ejemplo SpA",
                        "count": 2,
                        "total_clp": 1640000,
                        "unreviewed_count": 1,
                        "items": [
                          {
                            "kind": "link",
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e01",
                            "actor": "match",
                            "label": "Conciliado con Factura N° 4521",
                            "day": "2026-10-29",
                            "amount_clp": 1190000,
                            "applied_at": "2026-10-30T09:12:00.000Z",
                            "movement": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10",
                              "day": "2026-10-29",
                              "direction": "outbound",
                              "amount_clp": 1190000,
                              "description": "TRANSF A PROVEEDOR EJEMPLO SPA FACT 4521"
                            },
                            "document": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                              "kind": "purchase",
                              "dte_type": 33,
                              "folio": "4521",
                              "issue_date": "2026-10-01",
                              "total_clp": 1190000,
                              "counterparty_name": "Proveedor Ejemplo SpA"
                            },
                            "target": "document",
                            "reasons": [
                              "folio_and_amount"
                            ],
                            "own_transfer": null,
                            "account": null,
                            "split": false,
                            "rule": null,
                            "reviewed_at": null,
                            "reviewed_by": null
                          },
                          {
                            "kind": "classification",
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e02",
                            "actor": "rule",
                            "label": "Clasificado en Costos directos › Laboratorio",
                            "day": "2026-10-05",
                            "amount_clp": 450000,
                            "applied_at": "2026-10-06T08:00:00.000Z",
                            "movement": null,
                            "document": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d02",
                              "kind": "purchase",
                              "dte_type": 33,
                              "folio": "4410",
                              "issue_date": "2026-10-05",
                              "total_clp": 450000,
                              "counterparty_name": "Proveedor Ejemplo SpA"
                            },
                            "target": null,
                            "reasons": [],
                            "own_transfer": null,
                            "account": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                              "name": "Laboratorio",
                              "path": "Costos directos › Laboratorio"
                            },
                            "split": false,
                            "rule": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                              "name": null
                            },
                            "reviewed_at": "2026-10-07T13:00:00.000Z",
                            "reviewed_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01"
                          }
                        ],
                        "items_truncated": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/automatic-actions/review": {
      "post": {
        "summary": "Mark automatic actions reviewed (Marcar revisado)",
        "description": "Marks actions reviewed: the listed `items`, or every unreviewed action of a `month` (with `group`, only that group). Idempotent: already-reviewed actions are left as they are; `reviewed` counts the ones stamped now. A person-made link or classification is never counted. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "link",
                            "classification"
                          ],
                          "description": "link = a reconciliation link (Finanzas 08); classification = a classification by a rule or the IA (Finanzas 07)."
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "kind",
                        "id"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 500,
                    "description": "These actions (1..500)."
                  },
                  "month": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                    "description": "Every unreviewed action of the month (with `group`: of that group)."
                  },
                  "group": {
                    "type": "string",
                    "pattern": "^(?:contraparte:[0-9a-f-]{36}|own_transfers|cash_deposits|clinic_payments|none)$",
                    "description": "A group key as listed: `contraparte:<uuid>`, `own_transfers`, `cash_deposits`, `clinic_payments` or `none`."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "month": "2026-10",
                "group": "contraparte:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a20"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reviewed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAutomaticActionsReview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reviewed": 12
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/automatic-actions/{kind}/{id}/undo": {
      "post": {
        "summary": "Undo an automatic action (Deshacer)",
        "description": "Undoes one automatic action; its item is open again and returns to «Por resolver». A link is stamped undone (both sides get the amount back; the engine never re-applies that pair). A classification is removed with an audited unclassify by the person, and the rule never re-applies to that item. Undoing an IA action stores the teaching signal (`teaching_signal: true`) the IA reads as a counter-example. 404 when the id is not an automatic action; 409 when already undone or when the classification changed afterwards. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "links",
                "classifications"
              ],
              "description": "`links` for a link action, `classifications` for a classification action."
            },
            "required": true,
            "name": "kind",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 500,
              "description": "«Motivo del ajuste» (3..500 characters). Required when the change touches a closed month: without it the write answers 409 `FINANZAS_PERIOD_CLOSED` (`details.months`) and nothing is written; with it the change is applied and logged as an «ajuste posterior al cierre». Ignored when no closed month is touched."
            },
            "required": false,
            "name": "adjustment_reason",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Undone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAutomaticActionUndo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "link",
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4e01",
                    "actor": "match",
                    "item": {
                      "type": "movement",
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a10"
                    },
                    "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01",
                    "teaching_signal": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/own-account-suggestions": {
      "get": {
        "summary": "Own-account cards: «Agregar ··XXXX como cuenta propia»",
        "description": "D5: money that goes to the workspace's own RUT or between two of its accounts is a «Traspaso entre cuentas propias» that Vitrina applies by itself (in Hecho por Vitrina, undoable), never income, never a cost and never a case. When the destination account is not registered yet, one card offers to add it. Lists the destination accounts that live traspasos went to and that are neither registered nor dismissed, biggest first, with the masked number, the total and the count. An account disappears once registered (accept), dismissed («Ahora no») or when all its traspasos were undone. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The cards",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasOwnAccountSuggestions"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "suggestions": [
                      {
                        "key": "4521",
                        "last4": "4521",
                        "number_masked": "•••• 4521",
                        "bank_name": null,
                        "total_clp": 9950000,
                        "count": 3,
                        "first_day": "2026-10-13",
                        "last_day": "2026-11-05",
                        "title": "Agregar ··4521 como cuenta propia",
                        "body": "Le transferiste $9.950.000 a tu propio RUT. Si la cuenta es tuya, esos traspasos no cuentan como gasto."
                      }
                    ],
                    "count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/own-account-suggestions/{key}/accept": {
      "post": {
        "summary": "Add the suggested account as an own account",
        "description": "D5: money that goes to the workspace's own RUT or between two of its accounts is a «Traspaso entre cuentas propias» that Vitrina applies by itself (in Hecho por Vitrina, undoable), never income, never a cost and never a case. When the destination account is not registered yet, one card offers to add it. Adds the account to the registry as a Finanzas bank account (Cuentas; the same registry the agent shares from); its traspasos stay traspasos. Idempotent: an account already registered under that number is returned as it is. 404 `NOT_FOUND` when no traspaso went to that number. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{3,39}$",
              "description": "The suggestion `key`: the account number as digits, without leading zeros."
            },
            "required": true,
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasOwnAccountAcceptBody"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasBankAccount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4b01",
                    "name": "Cuenta ··4521"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/own-account-suggestions/{key}/dismiss": {
      "post": {
        "summary": "Hide the own-account card for that account («Ahora no»)",
        "description": "D5: money that goes to the workspace's own RUT or between two of its accounts is a «Traspaso entre cuentas propias» that Vitrina applies by itself (in Hecho por Vitrina, undoable), never income, never a cost and never a case. When the destination account is not registered yet, one card offers to add it. The card never comes back for that account; its traspasos stay traspasos. Idempotent. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{3,39}$",
              "description": "The suggestion `key`: the account number as digits, without leading zeros."
            },
            "required": true,
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasOwnAccountDismiss"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "key": "4521",
                    "dismissed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/months": {
      "get": {
        "summary": "Cierre de mes: each month and its state",
        "description": "The month selector, newest first: `open` (a «Por resolver» case belongs to the month, or it has not ended), `ready_to_close` (ended, nothing open), `closed` (a person closed it) or `closed_imported` («Cerrado antes de Vitrina»: the import closed it; it raises no cases and a change asks for a reason; `closed_source` `chipax`, `imported_at`). A case belongs to ONE month: the one of its oldest item (a movement, or a document to classify). Default: the current month and the 11 before; at most 24. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: 11 months before `to`)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: the current month). At most 24 months."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The months",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMonths"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "current_month": "2026-10",
                    "months": [
                      {
                        "month": "2026-10",
                        "label": "octubre 2026",
                        "state": "open",
                        "ended": false,
                        "open_cases": 7,
                        "open_cases_amount_clp": 2418300,
                        "closed_at": null,
                        "closed_by": null,
                        "closed_source": null,
                        "imported_at": null,
                        "adjustment_count": 0
                      },
                      {
                        "month": "2026-09",
                        "label": "septiembre 2026",
                        "state": "closed",
                        "ended": true,
                        "open_cases": 0,
                        "open_cases_amount_clp": 0,
                        "closed_at": "2026-10-03T14:20:00.000Z",
                        "closed_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                        "closed_source": "person",
                        "imported_at": null,
                        "adjustment_count": 1
                      },
                      {
                        "month": "2026-08",
                        "label": "agosto 2026",
                        "state": "ready_to_close",
                        "ended": true,
                        "open_cases": 0,
                        "open_cases_amount_clp": 0,
                        "closed_at": null,
                        "closed_by": null,
                        "closed_source": null,
                        "imported_at": null,
                        "adjustment_count": 0
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/months/{month}": {
      "get": {
        "summary": "Cierre de mes: one month, its close checklist and its close",
        "description": "The state, the checklist the close shows (open cases, automatic actions nobody reviewed in «Hecho por Vitrina», unclassified cost documents, movements nothing explains), `can_close` / `blocking_reason`, and once closed: who and when, the snapshot of the month's figures at close and what changed since (`changes_since_close`: logged adjustments, documents and movements that arrived after the close, documents voided after it). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The month",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMonthStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-09",
                    "label": "septiembre 2026",
                    "from": "2026-09-01",
                    "to": "2026-09-30",
                    "state": "closed",
                    "ended": true,
                    "open_cases": 0,
                    "open_cases_amount_clp": 0,
                    "closed_at": "2026-10-03T14:20:00.000Z",
                    "closed_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                    "closed_source": "person",
                    "imported_at": null,
                    "closed_by_name": "Ana Pérez",
                    "adjustment_count": 1,
                    "can_close": false,
                    "blocking_reason": "already_closed",
                    "checklist": {
                      "open_cases": 0,
                      "open_cases_amount_clp": 0,
                      "open_case_movements_count": 0,
                      "open_case_documents_count": 0,
                      "unreviewed_count": 4,
                      "unclassified_count": 0,
                      "unmatched_count": 0,
                      "unmatched_movements_count": 0,
                      "unmatched_documents_count": 0,
                      "unmatched_pending_clp": 0,
                      "unmatched_movements_pending_clp": 0,
                      "unmatched_documents_pending_clp": 0
                    },
                    "close": {
                      "unreviewed_count": 4,
                      "unclassified_count": 0,
                      "unmatched_count": 0,
                      "snapshot_version": 1,
                      "snapshot": {
                        "documents": {
                          "purchase": {
                            "count": 38,
                            "total_clp": 9840000
                          },
                          "sale": {
                            "count": 412,
                            "total_clp": 31250000
                          }
                        },
                        "movements": {
                          "inbound": {
                            "count": 220,
                            "total_clp": 30100000
                          },
                          "outbound": {
                            "count": 95,
                            "total_clp": 21400000
                          }
                        },
                        "accounts": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                            "count": 12,
                            "total_clp": 2380000
                          },
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                            "count": 5,
                            "total_clp": 640000
                          }
                        ],
                        "unclassified_documents": {
                          "count": 0
                        },
                        "unmatched_movements": {
                          "count": 0,
                          "pending_clp": 0
                        },
                        "unmatched_documents": {
                          "count": 0,
                          "pending_clp": 0
                        }
                      }
                    },
                    "changes_since_close": {
                      "adjustments": 1,
                      "documents_added": 1,
                      "documents_voided": 0,
                      "movements_added": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/months/{month}/close": {
      "post": {
        "summary": "Close a month (Cerrar mes)",
        "description": "Closes an ended month with nothing open in «Por resolver». Send the `checklist.unreviewed_count` the person confirmed. Records who, when, the checklist and a snapshot of the figures. Soft: nothing is locked; from now on a change that touches the month needs `?adjustment_reason=` and is listed as an «ajuste posterior al cierre». 409 `FINANZAS_MONTH_NOT_CLOSABLE` with `details.reason` `open_cases` (+ `open_cases`, the count), `not_ended`, `already_closed` or `unreviewed_changed` (+ `unreviewed_count`, the count now). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "unreviewed_count": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The `checklist.unreviewed_count` the person saw and confirmed. A different count now answers 409 `FINANZAS_MONTH_NOT_CLOSABLE` (`unreviewed_changed`) with the current one."
                  }
                },
                "required": [
                  "unreviewed_count"
                ],
                "additionalProperties": false
              },
              "example": {
                "unreviewed_count": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasMonthStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-09",
                    "label": "septiembre 2026",
                    "from": "2026-09-01",
                    "to": "2026-09-30",
                    "state": "closed",
                    "ended": true,
                    "open_cases": 0,
                    "open_cases_amount_clp": 0,
                    "closed_at": "2026-10-03T14:20:00.000Z",
                    "closed_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                    "closed_source": "person",
                    "imported_at": null,
                    "closed_by_name": "Ana Pérez",
                    "adjustment_count": 0,
                    "can_close": false,
                    "blocking_reason": "already_closed",
                    "checklist": {
                      "open_cases": 0,
                      "open_cases_amount_clp": 0,
                      "open_case_movements_count": 0,
                      "open_case_documents_count": 0,
                      "unreviewed_count": 4,
                      "unclassified_count": 0,
                      "unmatched_count": 0,
                      "unmatched_movements_count": 0,
                      "unmatched_documents_count": 0,
                      "unmatched_pending_clp": 0,
                      "unmatched_movements_pending_clp": 0,
                      "unmatched_documents_pending_clp": 0
                    },
                    "close": {
                      "unreviewed_count": 4,
                      "unclassified_count": 0,
                      "unmatched_count": 0,
                      "snapshot_version": 1,
                      "snapshot": {
                        "documents": {
                          "purchase": {
                            "count": 38,
                            "total_clp": 9840000
                          },
                          "sale": {
                            "count": 412,
                            "total_clp": 31250000
                          }
                        },
                        "movements": {
                          "inbound": {
                            "count": 220,
                            "total_clp": 30100000
                          },
                          "outbound": {
                            "count": 95,
                            "total_clp": 21400000
                          }
                        },
                        "accounts": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                            "count": 12,
                            "total_clp": 2380000
                          },
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                            "count": 5,
                            "total_clp": 640000
                          }
                        ],
                        "unclassified_documents": {
                          "count": 0
                        },
                        "unmatched_movements": {
                          "count": 0,
                          "pending_clp": 0
                        },
                        "unmatched_documents": {
                          "count": 0,
                          "pending_clp": 0
                        }
                      }
                    },
                    "changes_since_close": {
                      "adjustments": 0,
                      "documents_added": 0,
                      "documents_voided": 0,
                      "movements_added": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/months/{month}/adjustments": {
      "get": {
        "summary": "Ajustes posteriores al cierre of a month",
        "description": "Every change to the month after it was closed, newest first: a person's (with the reason they gave) or an automatic one (`rule`, `match`, `ai`, `import`; no reason). `before` / `after` carry the classification lines; `amount_clp` the amount of a link. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The adjustments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasPeriodAdjustments"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-09",
                    "closed": true,
                    "closed_at": "2026-10-03T14:20:00.000Z",
                    "total": 1,
                    "items": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4a90",
                        "month": "2026-09",
                        "change": "reclassify",
                        "label": "Reclasificado de Laboratorio a Insumos clínicos",
                        "actor": "person",
                        "actor_user_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                        "actor_name": "Ana Pérez",
                        "reason": "La factura era de insumos, no de laboratorio",
                        "item": {
                          "type": "document",
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d01"
                        },
                        "document": {
                          "kind": "purchase",
                          "dte_type": 33,
                          "folio": "4410",
                          "issue_date": "2026-09-05",
                          "total_clp": 450000,
                          "counterparty_name": "Proveedor Ejemplo SpA"
                        },
                        "movement": null,
                        "before": [
                          {
                            "account": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c01",
                              "name": "Laboratorio",
                              "path": "Costos directos › Laboratorio"
                            },
                            "amount_clp": 450000,
                            "period": "2026-09"
                          }
                        ],
                        "after": [
                          {
                            "account": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c02",
                              "name": "Insumos clínicos",
                              "path": "Costos directos › Insumos clínicos"
                            },
                            "amount_clp": 450000,
                            "period": "2026-09"
                          }
                        ],
                        "amount_clp": null,
                        "created_at": "2026-10-12T16:05:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/chipax/shadow": {
      "get": {
        "summary": "Mes en paralelo: the months read from Chipax",
        "description": "Each month with a Chipax read, newest first: its newest read (any status) and when it was last read successfully. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read` and `finanzas_reports:read` (the Contador seat, owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "Months",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "month": {
                            "type": "string"
                          },
                          "label": {
                            "type": "string",
                            "description": "«septiembre 2026»."
                          },
                          "latest_read": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "month": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "queued",
                                  "running",
                                  "done",
                                  "failed"
                                ]
                              },
                              "history_from": {
                                "type": "string",
                                "description": "Payables are compared among documents issued since this day (the earliest Chipax import, else a year before the month)."
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "requested_by": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "started_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "ISO 8601 timestamp."
                              },
                              "finished_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "ISO 8601 timestamp."
                              },
                              "created_at": {
                                "type": "string",
                                "description": "ISO 8601 timestamp."
                              }
                            },
                            "required": [
                              "id",
                              "month",
                              "status",
                              "history_from",
                              "error",
                              "requested_by",
                              "started_at",
                              "finished_at",
                              "created_at"
                            ]
                          },
                          "read_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "When the month was last read successfully."
                          }
                        },
                        "required": [
                          "month",
                          "label",
                          "latest_read",
                          "read_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "month": "2026-10",
                      "label": "octubre 2026",
                      "latest_read": {
                        "id": "01930000-0000-7000-8000-000000000017",
                        "month": "2026-10",
                        "status": "done",
                        "history_from": "2025-01-01",
                        "error": null,
                        "requested_by": "00000000-0000-4000-8000-000000000001",
                        "started_at": null,
                        "finished_at": null,
                        "created_at": "2026-11-02T12:00:00.000Z"
                      },
                      "read_at": "2026-11-02T12:04:10.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Mes en paralelo: read Chipax's side of a month",
        "description": "Queues a read of Chipax for the month (bank lines and balances, open payables, classified items, Resultado Operacional), over the documents issued since `history_from`. Takes minutes (Chipax's rate limit). One read at a time; refused while a Chipax import runs (409 `import_running`) or another read runs (409 `read_running`); 409 `not_connected` without Chipax credentials; 409 `chipax_credential_unreadable` when the stored credential cannot be decrypted (connect Chipax again). Vitrina only READS Chipax: it logs in and sends GET requests; it never creates, edits or deletes anything in the Chipax account. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "month": {
                    "type": "string",
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
                    "description": "The shadow month (default: the month before the current one, America/Santiago). Up to the current month; a future month answers 400."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "month": "2026-10"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Read queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "month": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "running",
                            "done",
                            "failed"
                          ]
                        },
                        "history_from": {
                          "type": "string",
                          "description": "Payables are compared among documents issued since this day (the earliest Chipax import, else a year before the month)."
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "requested_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "started_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "ISO 8601 timestamp."
                        },
                        "finished_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "ISO 8601 timestamp."
                        },
                        "created_at": {
                          "type": "string",
                          "description": "ISO 8601 timestamp."
                        }
                      },
                      "required": [
                        "id",
                        "month",
                        "status",
                        "history_from",
                        "error",
                        "requested_by",
                        "started_at",
                        "finished_at",
                        "created_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "01930000-0000-7000-8000-000000000017",
                    "month": "2026-10",
                    "status": "queued",
                    "history_from": "2025-01-01",
                    "error": null,
                    "requested_by": "00000000-0000-4000-8000-000000000001",
                    "started_at": null,
                    "finished_at": null,
                    "created_at": "2026-11-02T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/chipax/shadow/{month}": {
      "get": {
        "summary": "Mes en paralelo: the diff against Chipax for a month",
        "description": "Balances (per bank account, at month end), payables (per Contraparte, open at month end) and costs (per costo / gasto account, NET as the Resultado counts) must match to the peso. Income is a bridge: Chipax counts it when cashed, Vitrina the documents issued in the month, so the gap is explained line by line. Vitrina's side is computed live; Chipax's side is the newest finished read. `clean` = all three match and the bridge is explained (the exit criterion). Each row's `key` drills down to its items. Rows list the differences first; `difference_count` of each section counts its rows that are not «Cuadra», and the headline `difference_count` is the sum of the sections with `in_headline` (the review `possible_duplicates` is listed beside them and never counted in it; `inherited_overdue` is retired, Finanzas 50, and no longer listed). Before a finished read: `status` not_read / reading / failed and null dimensions. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» with no RUT and no glosa, on both sides; Chipax boletas carry neither RUT nor name. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read` and `finanzas_reports:read` (the Contador seat, owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The diff",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "not_read",
                            "reading",
                            "ready",
                            "failed"
                          ]
                        },
                        "read": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "month": {
                              "type": "string"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "queued",
                                "running",
                                "done",
                                "failed"
                              ]
                            },
                            "history_from": {
                              "type": "string",
                              "description": "Payables are compared among documents issued since this day (the earliest Chipax import, else a year before the month)."
                            },
                            "error": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "requested_by": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "started_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "ISO 8601 timestamp."
                            },
                            "finished_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "ISO 8601 timestamp."
                            },
                            "created_at": {
                              "type": "string",
                              "description": "ISO 8601 timestamp."
                            }
                          },
                          "required": [
                            "id",
                            "month",
                            "status",
                            "history_from",
                            "error",
                            "requested_by",
                            "started_at",
                            "finished_at",
                            "created_at"
                          ]
                        },
                        "active_read": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "month": {
                              "type": "string"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "queued",
                                "running",
                                "done",
                                "failed"
                              ]
                            },
                            "history_from": {
                              "type": "string",
                              "description": "Payables are compared among documents issued since this day (the earliest Chipax import, else a year before the month)."
                            },
                            "error": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "requested_by": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "started_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "ISO 8601 timestamp."
                            },
                            "finished_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "ISO 8601 timestamp."
                            },
                            "created_at": {
                              "type": "string",
                              "description": "ISO 8601 timestamp."
                            }
                          },
                          "required": [
                            "id",
                            "month",
                            "status",
                            "history_from",
                            "error",
                            "requested_by",
                            "started_at",
                            "finished_at",
                            "created_at"
                          ]
                        },
                        "last_error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "history_from": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "clean": {
                          "type": [
                            "boolean",
                            "null"
                          ],
                          "description": "Balances, payables and costs match and the income bridge is fully explained: the exit criterion of the shadow month."
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "difference_count": {
                          "type": "integer",
                          "description": "The headline «{n} diferencias en {month}»: the sum of `difference_count` of the `sections` with `in_headline`. 0 until Chipax has been read."
                        },
                        "sections": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "balances",
                                  "payables",
                                  "costs",
                                  "income",
                                  "inherited_overdue",
                                  "possible_duplicates"
                                ]
                              },
                              "difference_count": {
                                "type": "integer"
                              },
                              "in_headline": {
                                "type": "boolean",
                                "description": "true for the four comparisons of Chipax against Vitrina; false for the review `possible_duplicates` (groups of repeated bank lines, see GET /finanzas/possible-duplicates), which is a to-do list. `inherited_overdue` is retired (Finanzas 50: those payables are «Pagado antes de Vitrina») and never listed."
                              }
                            },
                            "required": [
                              "key",
                              "difference_count",
                              "in_headline"
                            ]
                          },
                          "description": "Every section in screen order. The two reviews are present even before Chipax is read."
                        },
                        "balances": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "matches": {
                              "type": "boolean"
                            },
                            "difference_count": {
                              "type": "integer",
                              "description": "The section's «N diferencias»: its rows that are not «Cuadra» (Finanzas 40). Income counts its bridge lines that hold items."
                            },
                            "row_count": {
                              "type": "integer",
                              "description": "Rows the section lists (income: bridge lines)."
                            },
                            "matching_count": {
                              "type": "integer",
                              "description": "Rows that match, for «{n} cuentas cuadran · Ver». `rows` lists the differences first."
                            },
                            "item_difference_count": {
                              "type": "integer"
                            },
                            "without_balance_count": {
                              "type": "integer",
                              "description": "Rows with no balance on one side (compared on the month movements): the «un lado no tiene saldo» footnote, once per section."
                            },
                            "rows": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "description": "Pass as `key` to the items endpoint."
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "chipax_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "vitrina_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "difference_clp": {
                                    "type": "integer",
                                    "description": "Chipax − Vitrina."
                                  },
                                  "matches": {
                                    "type": "boolean",
                                    "description": "Zero tolerance: equal figures AND every item pairs."
                                  },
                                  "item_count": {
                                    "type": "integer"
                                  },
                                  "difference_count": {
                                    "type": "integer"
                                  },
                                  "side": {
                                    "type": "string",
                                    "enum": [
                                      "paired",
                                      "only_vitrina",
                                      "only_chipax"
                                    ],
                                    "description": "paired: both sides have the account (zero tolerance). only_vitrina: Chipax never tracked it (e.g. Mercado Pago); shown, never breaks `clean`, difference_clp is 0 and it has no items. only_chipax: Vitrina lacks the account; breaks `clean`."
                                  },
                                  "basis": {
                                    "type": "string",
                                    "enum": [
                                      "closing",
                                      "movements"
                                    ],
                                    "description": "closing = balances at month end; movements = the month's net movements (a side has no balance)."
                                  },
                                  "vitrina_account": {
                                    "type": [
                                      "object",
                                      "null"
                                    ],
                                    "properties": {
                                      "id": {
                                        "type": "string"
                                      },
                                      "name": {
                                        "type": "string"
                                      },
                                      "institution_name": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      },
                                      "number_masked": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "name",
                                      "institution_name",
                                      "number_masked"
                                    ]
                                  },
                                  "chipax_account": {
                                    "type": [
                                      "object",
                                      "null"
                                    ],
                                    "properties": {
                                      "external_id": {
                                        "type": "string",
                                        "description": "Chipax's id of the account."
                                      },
                                      "name": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      },
                                      "bank": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      },
                                      "number_masked": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      }
                                    },
                                    "required": [
                                      "external_id",
                                      "name",
                                      "bank",
                                      "number_masked"
                                    ]
                                  },
                                  "movements": {
                                    "type": "object",
                                    "properties": {
                                      "chipax_in_clp": {
                                        "type": "integer",
                                        "description": "Whole CLP."
                                      },
                                      "chipax_out_clp": {
                                        "type": "integer",
                                        "description": "Whole CLP."
                                      },
                                      "vitrina_in_clp": {
                                        "type": "integer",
                                        "description": "Whole CLP."
                                      },
                                      "vitrina_out_clp": {
                                        "type": "integer",
                                        "description": "Whole CLP."
                                      }
                                    },
                                    "required": [
                                      "chipax_in_clp",
                                      "chipax_out_clp",
                                      "vitrina_in_clp",
                                      "vitrina_out_clp"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "chipax_clp",
                                  "vitrina_clp",
                                  "difference_clp",
                                  "matches",
                                  "item_count",
                                  "difference_count",
                                  "side",
                                  "basis",
                                  "vitrina_account",
                                  "chipax_account",
                                  "movements"
                                ]
                              }
                            }
                          },
                          "required": [
                            "matches",
                            "difference_count",
                            "row_count",
                            "matching_count",
                            "item_difference_count",
                            "without_balance_count",
                            "rows"
                          ]
                        },
                        "payables": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "matches": {
                              "type": "boolean"
                            },
                            "chipax_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "vitrina_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "difference_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "difference_count": {
                              "type": "integer",
                              "description": "The section's «N diferencias»: its rows that are not «Cuadra» (Finanzas 40). Income counts its bridge lines that hold items."
                            },
                            "row_count": {
                              "type": "integer",
                              "description": "Rows the section lists (income: bridge lines)."
                            },
                            "matching_count": {
                              "type": "integer",
                              "description": "Rows that match, for «{n} cuentas cuadran · Ver». `rows` lists the differences first."
                            },
                            "item_difference_count": {
                              "type": "integer",
                              "description": "Items (lines, documents) behind the rows that do not pair."
                            },
                            "rows": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "description": "Pass as `key` to the items endpoint."
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "chipax_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "vitrina_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "difference_clp": {
                                    "type": "integer",
                                    "description": "Chipax − Vitrina."
                                  },
                                  "matches": {
                                    "type": "boolean",
                                    "description": "Zero tolerance: equal figures AND every item pairs."
                                  },
                                  "item_count": {
                                    "type": "integer"
                                  },
                                  "difference_count": {
                                    "type": "integer"
                                  },
                                  "rut": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "contraparte_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "chipax_clp",
                                  "vitrina_clp",
                                  "difference_clp",
                                  "matches",
                                  "item_count",
                                  "difference_count",
                                  "rut",
                                  "contraparte_id"
                                ]
                              }
                            }
                          },
                          "required": [
                            "matches",
                            "chipax_clp",
                            "vitrina_clp",
                            "difference_clp",
                            "difference_count",
                            "row_count",
                            "matching_count",
                            "item_difference_count",
                            "rows"
                          ]
                        },
                        "costs": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "matches": {
                              "type": "boolean"
                            },
                            "chipax_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "vitrina_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "difference_clp": {
                              "type": "integer",
                              "description": "Whole CLP."
                            },
                            "difference_count": {
                              "type": "integer",
                              "description": "The section's «N diferencias»: its rows that are not «Cuadra» (Finanzas 40). Income counts its bridge lines that hold items."
                            },
                            "row_count": {
                              "type": "integer",
                              "description": "Rows the section lists (income: bridge lines)."
                            },
                            "matching_count": {
                              "type": "integer",
                              "description": "Rows that match, for «{n} cuentas cuadran · Ver». `rows` lists the differences first."
                            },
                            "item_difference_count": {
                              "type": "integer",
                              "description": "Items (lines, documents) behind the rows that do not pair."
                            },
                            "rows": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "description": "Pass as `key` to the items endpoint."
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "chipax_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "vitrina_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "difference_clp": {
                                    "type": "integer",
                                    "description": "Chipax − Vitrina."
                                  },
                                  "matches": {
                                    "type": "boolean",
                                    "description": "Zero tolerance: equal figures AND every item pairs."
                                  },
                                  "item_count": {
                                    "type": "integer"
                                  },
                                  "difference_count": {
                                    "type": "integer"
                                  },
                                  "vitrina_account": {
                                    "type": [
                                      "object",
                                      "null"
                                    ],
                                    "properties": {
                                      "id": {
                                        "type": "string"
                                      },
                                      "name": {
                                        "type": "string"
                                      },
                                      "path": {
                                        "type": "string"
                                      },
                                      "kind": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "name",
                                      "path",
                                      "kind"
                                    ]
                                  },
                                  "chipax_accounts": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "external_id": {
                                          "type": "integer"
                                        },
                                        "name": {
                                          "type": "string"
                                        },
                                        "path": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "external_id",
                                        "name",
                                        "path"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "chipax_clp",
                                  "vitrina_clp",
                                  "difference_clp",
                                  "matches",
                                  "item_count",
                                  "difference_count",
                                  "vitrina_account",
                                  "chipax_accounts"
                                ]
                              }
                            }
                          },
                          "required": [
                            "matches",
                            "chipax_clp",
                            "vitrina_clp",
                            "difference_clp",
                            "difference_count",
                            "row_count",
                            "matching_count",
                            "item_difference_count",
                            "rows"
                          ]
                        },
                        "income": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "difference_count": {
                              "type": "integer",
                              "description": "The section's «N diferencias»: its rows that are not «Cuadra» (Finanzas 40). Income counts its bridge lines that hold items."
                            },
                            "row_count": {
                              "type": "integer",
                              "description": "Rows the section lists (income: bridge lines)."
                            },
                            "matching_count": {
                              "type": "integer",
                              "description": "Rows that match, for «{n} cuentas cuadran · Ver». `rows` lists the differences first."
                            },
                            "bridge": {
                              "type": "object",
                              "properties": {
                                "vitrina_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "chipax_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "difference_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "explained": {
                                  "type": "boolean"
                                },
                                "lines": {
                                  "type": "array",
                                  "items": {
                                    "type": "object",
                                    "properties": {
                                      "key": {
                                        "type": "string",
                                        "enum": [
                                          "timing_vitrina",
                                          "timing_chipax",
                                          "document_not_in_vitrina",
                                          "cash_without_document",
                                          "amount_basis",
                                          "unitemised"
                                        ]
                                      },
                                      "label": {
                                        "type": "string"
                                      },
                                      "amount_clp": {
                                        "type": "integer",
                                        "description": "Whole CLP."
                                      },
                                      "item_count": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "key",
                                      "label",
                                      "amount_clp",
                                      "item_count"
                                    ]
                                  }
                                }
                              },
                              "required": [
                                "vitrina_clp",
                                "chipax_clp",
                                "difference_clp",
                                "explained",
                                "lines"
                              ]
                            },
                            "rows": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "description": "Pass as `key` to the items endpoint."
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "chipax_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "vitrina_clp": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ],
                                    "description": "Whole CLP."
                                  },
                                  "difference_clp": {
                                    "type": "integer",
                                    "description": "Chipax − Vitrina."
                                  },
                                  "matches": {
                                    "type": "boolean",
                                    "description": "Zero tolerance: equal figures AND every item pairs."
                                  },
                                  "item_count": {
                                    "type": "integer"
                                  },
                                  "difference_count": {
                                    "type": "integer"
                                  },
                                  "vitrina_account": {
                                    "type": [
                                      "object",
                                      "null"
                                    ],
                                    "properties": {
                                      "id": {
                                        "type": "string"
                                      },
                                      "name": {
                                        "type": "string"
                                      },
                                      "path": {
                                        "type": "string"
                                      },
                                      "kind": {
                                        "type": "string"
                                      }
                                    },
                                    "required": [
                                      "id",
                                      "name",
                                      "path",
                                      "kind"
                                    ]
                                  },
                                  "chipax_accounts": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "external_id": {
                                          "type": "integer"
                                        },
                                        "name": {
                                          "type": "string"
                                        },
                                        "path": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "external_id",
                                        "name",
                                        "path"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "key",
                                  "label",
                                  "chipax_clp",
                                  "vitrina_clp",
                                  "difference_clp",
                                  "matches",
                                  "item_count",
                                  "difference_count",
                                  "vitrina_account",
                                  "chipax_accounts"
                                ]
                              }
                            },
                            "production": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "produccion_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "por_cobrar_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "anticipos_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "cobrado_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "boleteado_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "cobrado_sin_boleta_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                },
                                "boletas_sin_pago_clp": {
                                  "type": "integer",
                                  "description": "Whole CLP."
                                }
                              },
                              "required": [
                                "produccion_clp",
                                "por_cobrar_clp",
                                "anticipos_clp",
                                "cobrado_clp",
                                "boleteado_clp",
                                "cobrado_sin_boleta_clp",
                                "boletas_sin_pago_clp"
                              ]
                            }
                          },
                          "required": [
                            "difference_count",
                            "row_count",
                            "matching_count",
                            "bridge",
                            "rows",
                            "production"
                          ]
                        }
                      },
                      "required": [
                        "month",
                        "label",
                        "status",
                        "read",
                        "active_read",
                        "last_error",
                        "history_from",
                        "clean",
                        "warnings",
                        "difference_count",
                        "sections",
                        "balances",
                        "payables",
                        "costs",
                        "income"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "label": "octubre 2026",
                    "status": "ready",
                    "read": {
                      "id": "01930000-0000-7000-8000-000000000017",
                      "month": "2026-10",
                      "status": "done",
                      "history_from": "2025-01-01",
                      "error": null,
                      "requested_by": "00000000-0000-4000-8000-000000000001",
                      "started_at": null,
                      "finished_at": null,
                      "created_at": "2026-11-02T12:00:00.000Z"
                    },
                    "active_read": null,
                    "last_error": null,
                    "history_from": "2025-01-01",
                    "clean": false,
                    "warnings": [],
                    "difference_count": 1,
                    "sections": [
                      {
                        "key": "balances",
                        "difference_count": 0,
                        "in_headline": true
                      },
                      {
                        "key": "payables",
                        "difference_count": 1,
                        "in_headline": true
                      },
                      {
                        "key": "costs",
                        "difference_count": 0,
                        "in_headline": true
                      },
                      {
                        "key": "income",
                        "difference_count": 2,
                        "in_headline": true
                      },
                      {
                        "key": "possible_duplicates",
                        "difference_count": 2,
                        "in_headline": false
                      }
                    ],
                    "balances": {
                      "matches": true,
                      "difference_count": 0,
                      "row_count": 1,
                      "matching_count": 1,
                      "item_difference_count": 0,
                      "without_balance_count": 0,
                      "rows": [
                        {
                          "key": "bank:01930000-0000-7000-8000-0000000000b1",
                          "label": "Cuenta corriente",
                          "chipax_clp": 12480300,
                          "vitrina_clp": 12480300,
                          "difference_clp": 0,
                          "matches": true,
                          "item_count": 212,
                          "difference_count": 0,
                          "basis": "closing",
                          "vitrina_account": {
                            "id": "01930000-0000-7000-8000-0000000000b1",
                            "name": "Cuenta corriente",
                            "institution_name": "Banco de Chile",
                            "number_masked": "•••• 4321"
                          },
                          "chipax_account": {
                            "external_id": "881",
                            "name": "Cuenta corriente",
                            "bank": "Banco de Chile",
                            "number_masked": "•••• 4321"
                          },
                          "movements": {
                            "chipax_in_clp": 30100000,
                            "chipax_out_clp": 27620000,
                            "vitrina_in_clp": 30100000,
                            "vitrina_out_clp": 27620000
                          }
                        }
                      ]
                    },
                    "payables": {
                      "matches": false,
                      "chipax_clp": 4210000,
                      "vitrina_clp": 3800000,
                      "difference_clp": 410000,
                      "difference_count": 1,
                      "row_count": 1,
                      "matching_count": 0,
                      "item_difference_count": 1,
                      "rows": [
                        {
                          "key": "p_0000000000000001",
                          "label": "Laboratorio Dental Andes Ltda",
                          "chipax_clp": 410000,
                          "vitrina_clp": null,
                          "difference_clp": 410000,
                          "matches": false,
                          "item_count": 1,
                          "difference_count": 1,
                          "rut": "76086428-5",
                          "contraparte_id": null
                        }
                      ]
                    },
                    "costs": {
                      "matches": true,
                      "chipax_clp": 9340000,
                      "vitrina_clp": 9340000,
                      "difference_clp": 0,
                      "difference_count": 0,
                      "row_count": 0,
                      "matching_count": 0,
                      "item_difference_count": 0,
                      "rows": []
                    },
                    "income": {
                      "difference_count": 2,
                      "row_count": 6,
                      "matching_count": 4,
                      "bridge": {
                        "vitrina_clp": 31500000,
                        "chipax_clp": 30100000,
                        "difference_clp": -1400000,
                        "explained": true,
                        "lines": [
                          {
                            "key": "timing_vitrina",
                            "label": "Emitidos en el mes que Chipax no cuenta en el mes (los cuenta al cobrarlos)",
                            "amount_clp": -2100000,
                            "item_count": 14
                          },
                          {
                            "key": "timing_chipax",
                            "label": "Documentos de otros meses que Chipax cuenta en este mes",
                            "amount_clp": 700000,
                            "item_count": 5
                          }
                        ]
                      },
                      "rows": [],
                      "production": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/chipax/shadow/{month}/items": {
      "get": {
        "summary": "Mes en paralelo: the items behind a row",
        "description": "The items of one row of the diff (`key` from the diff; `bridge:<line>` for an income bridge line), both sides side by side, biggest difference first. By default only the items that do not match. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» with no RUT and no glosa, on both sides; Chipax boletas carry neither RUT nor name. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read` and `finanzas_reports:read` (the Contador seat, owners and admins). 404 before a finished read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "balances",
                "payables",
                "costs",
                "income"
              ],
              "description": "balances = per bank account; payables = per Contraparte; costs = per account (costo / gasto); income = per ingreso account, or a bridge line with `key=bridge:<line>`."
            },
            "required": true,
            "name": "dimension",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "A row `key` from the diff (a bank account, a Contraparte, an account), or `bridge:<line>` (income). Omitted: every item of the dimension."
            },
            "required": false,
            "name": "key",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "differences",
                "all"
              ],
              "description": "differences (default) = only items that do not match; all = also the matching ones."
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "dimension": {
                          "type": "string",
                          "enum": [
                            "balances",
                            "payables",
                            "costs",
                            "income"
                          ]
                        },
                        "key": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "row_key": {
                                "type": "string"
                              },
                              "other_row_key": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "match",
                                  "chipax_only",
                                  "vitrina_only",
                                  "amount",
                                  "other_row",
                                  "unitemised"
                                ],
                                "description": "match; chipax_only / vitrina_only; amount = both, different amounts; other_row = the other side has it under another row (account or Contraparte); unitemised = Chipax's total holds pesos with no item behind them."
                              },
                              "chipax": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "enum": [
                                      "document",
                                      "movement",
                                      "total"
                                    ]
                                  },
                                  "id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Vitrina: the document or bank movement id. Chipax: null."
                                  },
                                  "chipax_ref": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Chipax: `<model>:<id>` or `cartola:<id>`. Vitrina: null."
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Factura electrónica N° 451»."
                                  },
                                  "dte_type": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ]
                                  },
                                  "day": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "amount_clp": {
                                    "type": "integer",
                                    "description": "Signed, as the dimension counts it (money in +, out −, on balances)."
                                  },
                                  "counterparty_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "counterparty_rut": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "description": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "type",
                                  "id",
                                  "chipax_ref",
                                  "label",
                                  "dte_type",
                                  "day",
                                  "amount_clp",
                                  "counterparty_name",
                                  "counterparty_rut",
                                  "description"
                                ]
                              },
                              "vitrina": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "type": {
                                    "type": "string",
                                    "enum": [
                                      "document",
                                      "movement",
                                      "total"
                                    ]
                                  },
                                  "id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Vitrina: the document or bank movement id. Chipax: null."
                                  },
                                  "chipax_ref": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Chipax: `<model>:<id>` or `cartola:<id>`. Vitrina: null."
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Factura electrónica N° 451»."
                                  },
                                  "dte_type": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ]
                                  },
                                  "day": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "amount_clp": {
                                    "type": "integer",
                                    "description": "Signed, as the dimension counts it (money in +, out −, on balances)."
                                  },
                                  "counterparty_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "counterparty_rut": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "description": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "type",
                                  "id",
                                  "chipax_ref",
                                  "label",
                                  "dte_type",
                                  "day",
                                  "amount_clp",
                                  "counterparty_name",
                                  "counterparty_rut",
                                  "description"
                                ]
                              },
                              "vitrina_document_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "difference_clp": {
                                "type": "integer",
                                "description": "Chipax − Vitrina."
                              },
                              "bridge_line": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "timing_vitrina",
                                  "timing_chipax",
                                  "document_not_in_vitrina",
                                  "cash_without_document",
                                  "amount_basis",
                                  "unitemised"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "row_key",
                              "other_row_key",
                              "status",
                              "chipax",
                              "vitrina",
                              "vitrina_document_id",
                              "difference_clp",
                              "bridge_line"
                            ]
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "difference_clp": {
                          "type": "integer",
                          "description": "Whole CLP."
                        }
                      },
                      "required": [
                        "month",
                        "dimension",
                        "key",
                        "items",
                        "total",
                        "limit",
                        "offset",
                        "difference_clp"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "dimension": "payables",
                    "key": "p_0000000000000001",
                    "items": [
                      {
                        "key": "chipax:compra:9002",
                        "row_key": "p_0000000000000001",
                        "other_row_key": null,
                        "status": "chipax_only",
                        "chipax": {
                          "type": "document",
                          "id": null,
                          "chipax_ref": "compra:9002",
                          "label": "Factura electrónica N° 452",
                          "dte_type": 33,
                          "day": "2026-10-05",
                          "amount_clp": 410000,
                          "counterparty_name": "Laboratorio Dental Andes Ltda",
                          "counterparty_rut": "76086428-5",
                          "description": null
                        },
                        "vitrina": null,
                        "vitrina_document_id": null,
                        "difference_clp": 410000,
                        "bridge_line": null
                      }
                    ],
                    "total": 1,
                    "limit": 50,
                    "offset": 0,
                    "difference_clp": 410000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/chipax/shadow/{month}/export": {
      "get": {
        "summary": "Mes en paralelo: download the diff (xlsx)",
        "description": "One workbook: «Resumen», «Saldos bancarios», «Por pagar», «Costos y gastos», «Puente de ingresos» and «Diferencias» (every item that does not match, all dimensions). A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» with no RUT and no glosa, on both sides; Chipax boletas carry neither RUT nor name. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`, `finanzas_reports:read` and `finanzas:export`. 404 before a finished read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM`."
            },
            "required": true,
            "name": "month",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The workbook, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/inherited-overdue": {
      "get": {
        "summary": "Vencido heredado (retired): the payables to review",
        "deprecated": true,
        "description": "Retired (Finanzas 50): nothing writes «vencido heredado» any more; the payables it held are «Pagado antes de Vitrina» (GET /finanzas/documents?paid_before_vitrina=true), and only rows written before it in a workspace outside that backfill still show here. It listed the payables that arrived already more than 90 days overdue (`payment_state = vencido_heredado`): they are out of the cash-flow projection and the balances until someone marks them pagado or anulado. Each carries the open bank payment that most likely settled it (`suggested_match`: exact amount, a payment is suggested to one document only; `signal` says why). `summary` counts the whole filter, `items` is one page. Filter by Contraparte with `contraparte`. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» and no glosa on money in. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only the inherited payables of this Contraparte (`GET /finanzas/contrapartes` id). The Mes en paralelo tab reads it from `?contraparte=<id>`."
            },
            "required": false,
            "name": "contraparte",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "due_date",
                "amount"
              ],
              "description": "due_date (default): the oldest due date first. amount: the largest open amount first."
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The page",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "summary": {
                          "type": "object",
                          "properties": {
                            "count": {
                              "type": "integer"
                            },
                            "amount_clp": {
                              "type": "integer",
                              "description": "What is still open on them (total − retención − links)."
                            }
                          },
                          "required": [
                            "count",
                            "amount_clp"
                          ],
                          "description": "The whole filter, not only this page."
                        },
                        "filter": {
                          "type": "object",
                          "properties": {
                            "contraparte_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "contraparte_id"
                          ]
                        },
                        "items": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "purchase",
                                  "honorario",
                                  "manual_expense",
                                  "sale",
                                  "tax"
                                ]
                              },
                              "dte_type": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "«Factura electrónica N° 451»."
                              },
                              "folio": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "contraparte": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "id",
                                  "name"
                                ]
                              },
                              "counterparty_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "issue_date": {
                                "type": "string"
                              },
                              "due_date": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "arrived_on": {
                                "type": "string",
                                "description": "The day Vitrina first recorded it."
                              },
                              "days_overdue": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              },
                              "total_clp": {
                                "type": "integer",
                                "description": "Whole CLP."
                              },
                              "outstanding_clp": {
                                "type": "integer",
                                "description": "Whole CLP."
                              },
                              "suggested_match": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "movement_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "day": {
                                    "type": "string"
                                  },
                                  "amount_clp": {
                                    "type": "integer",
                                    "description": "Whole CLP."
                                  },
                                  "description": {
                                    "type": "string"
                                  },
                                  "counterparty_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bank_account_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bank_account_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "institution_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "The bank, for its logo («BCI»)."
                                  },
                                  "number_masked": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "signal": {
                                    "type": "string",
                                    "enum": [
                                      "folio",
                                      "contraparte",
                                      "amount"
                                    ],
                                    "description": "What ties the payment to the document: its folio in the glosa, the same Contraparte, or only the exact amount."
                                  }
                                },
                                "required": [
                                  "movement_id",
                                  "day",
                                  "amount_clp",
                                  "description",
                                  "counterparty_name",
                                  "bank_account_id",
                                  "bank_account_name",
                                  "institution_name",
                                  "number_masked",
                                  "signal"
                                ],
                                "description": "The open outbound bank line that most likely paid it (exact amount; one payment is suggested to one document). `null` when none fits."
                              }
                            },
                            "required": [
                              "id",
                              "kind",
                              "dte_type",
                              "label",
                              "folio",
                              "contraparte",
                              "counterparty_name",
                              "issue_date",
                              "due_date",
                              "arrived_on",
                              "days_overdue",
                              "total_clp",
                              "outstanding_clp",
                              "suggested_match"
                            ]
                          }
                        },
                        "total": {
                          "type": "integer"
                        },
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "summary",
                        "filter",
                        "items",
                        "total",
                        "limit",
                        "offset"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "summary": {
                      "count": 1135,
                      "amount_clp": 437600000
                    },
                    "filter": {
                      "contraparte_id": null
                    },
                    "items": [
                      {
                        "id": "01930000-0000-7000-8000-0000000000d1",
                        "kind": "purchase",
                        "dte_type": 33,
                        "label": "Factura electrónica N° 451",
                        "folio": "451",
                        "contraparte": {
                          "id": "01930000-0000-7000-8000-0000000000c1",
                          "name": "Laboratorio Dental Andes Ltda"
                        },
                        "counterparty_name": "Laboratorio Dental Andes Ltda",
                        "issue_date": "2025-06-10",
                        "due_date": "2025-07-10",
                        "arrived_on": "2025-11-02",
                        "days_overdue": 120,
                        "total_clp": 302852,
                        "outstanding_clp": 302852,
                        "suggested_match": {
                          "movement_id": "01930000-0000-7000-8000-0000000000a1",
                          "day": "2025-09-12",
                          "amount_clp": 302852,
                          "description": "Transferencia a Laboratorio Dental Andes",
                          "counterparty_name": "Laboratorio Dental Andes Ltda",
                          "bank_account_id": "01930000-0000-7000-8000-0000000000b1",
                          "bank_account_name": "Cuenta corriente",
                          "institution_name": "BCI",
                          "number_masked": "•••• 9819",
                          "signal": "contraparte"
                        }
                      }
                    ],
                    "total": 1135,
                    "limit": 50,
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/inherited-overdue/resolve": {
      "post": {
        "summary": "Vencido heredado (retired): mark pagado or anulado in bulk",
        "deprecated": true,
        "description": "Retired (Finanzas 50): only rows still «vencido heredado» (written before it, outside the Finanzas 50 backfill) can be resolved; anything else comes back skipped (`not_inherited`). It resolved up to 500 inherited payables at once. `pagado`: it was settled; the document leaves the review as paid (no bank link is invented). `anulado`: it never has to be paid; the document is voided, so no report counts it. A document that is no longer inherited is skipped (`not_inherited`), never overwritten. One batch, one `batch_id`: it lands in the audit log (`finanzas.inherited_overdue.pagado` / `.anulado`) and `POST /finanzas/inherited-overdue/batches/{batchId}/undo` puts it back. «anulado» in a closed month needs `?adjustment_reason=` (409 `FINANZAS_PERIOD_CLOSED` otherwise). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500,
                    "description": "The inherited payables to resolve (1 to 500)."
                  },
                  "resolution": {
                    "type": "string",
                    "enum": [
                      "pagado",
                      "anulado"
                    ],
                    "description": "pagado: it was settled (no bank link is invented; the document leaves the review as paid). anulado: it never has to be paid (it is voided, so no report counts it)."
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "document_ids",
                  "resolution"
                ],
                "additionalProperties": false
              },
              "example": {
                "document_ids": [
                  "01930000-0000-7000-8000-0000000000d1"
                ],
                "resolution": "pagado"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "batch_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "Pass it to the undo route («Deshacer»)."
                        },
                        "resolution": {
                          "type": "string",
                          "enum": [
                            "pagado",
                            "anulado"
                          ]
                        },
                        "resolved": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "reason": {
                                "type": "string",
                                "enum": [
                                  "not_found",
                                  "not_inherited"
                                ],
                                "description": "not_inherited: someone already resolved it, or a payment was linked to it."
                              }
                            },
                            "required": [
                              "id",
                              "reason"
                            ]
                          }
                        },
                        "amount_clp": {
                          "type": "integer",
                          "description": "What was open on the documents resolved."
                        },
                        "remaining": {
                          "type": "object",
                          "properties": {
                            "count": {
                              "type": "integer"
                            },
                            "amount_clp": {
                              "type": "integer",
                              "description": "What is still open on them (total − retención − links)."
                            }
                          },
                          "required": [
                            "count",
                            "amount_clp"
                          ]
                        }
                      },
                      "required": [
                        "batch_id",
                        "resolution",
                        "resolved",
                        "skipped",
                        "amount_clp",
                        "remaining"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "batch_id": "01930000-0000-7000-8000-0000000000b9",
                    "resolution": "pagado",
                    "resolved": [
                      "01930000-0000-7000-8000-0000000000d1"
                    ],
                    "skipped": [],
                    "amount_clp": 302852,
                    "remaining": {
                      "count": 1134,
                      "amount_clp": 437297148
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/inherited-overdue/batches/{batchId}/undo": {
      "post": {
        "summary": "Undo a settle batch («Deshacer»)",
        "description": "Puts the documents of a settle batch (a person's «Marcar pagada», or a Mes en paralelo review) back as they were (a payment linked in the meantime wins and the state follows it). To reopen one document, POST /finanzas/documents/{id}/reopen. Pass `document_ids` to undo only some. Idempotent: what was already undone comes back in `skipped`. Audited as `finanzas.inherited_overdue.undo`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "batchId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "document_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500,
                    "description": "Undo only these documents of the batch (default: the whole batch)."
                  }
                },
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The undo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "batch_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "resolution": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "pagado",
                            "anulado"
                          ]
                        },
                        "undone": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "remaining": {
                          "type": "object",
                          "properties": {
                            "count": {
                              "type": "integer"
                            },
                            "amount_clp": {
                              "type": "integer",
                              "description": "What is still open on them (total − retención − links)."
                            }
                          },
                          "required": [
                            "count",
                            "amount_clp"
                          ]
                        }
                      },
                      "required": [
                        "batch_id",
                        "resolution",
                        "undone",
                        "skipped",
                        "remaining"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "batch_id": "01930000-0000-7000-8000-0000000000b9",
                    "resolution": "pagado",
                    "undone": [
                      "01930000-0000-7000-8000-0000000000d1"
                    ],
                    "skipped": [],
                    "remaining": {
                      "count": 1135,
                      "amount_clp": 437600000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/inherited-overdue/batches": {
      "get": {
        "summary": "Settle batches a person made",
        "description": "Each bulk action a person made («Marcar pagada», Mes en paralelo), newest first; «Pagado antes de Vitrina» is the import's, never listed here, with how many of its documents are still resolved (`live_count`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Batches",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "batch_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "resolution": {
                            "type": "string",
                            "enum": [
                              "pagado",
                              "anulado"
                            ]
                          },
                          "resolved_at": {
                            "type": "string",
                            "description": "ISO 8601 timestamp."
                          },
                          "resolved_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "document_count": {
                            "type": "integer"
                          },
                          "live_count": {
                            "type": "integer",
                            "description": "Documents of the batch not undone."
                          },
                          "amount_clp": {
                            "type": "integer",
                            "description": "Whole CLP."
                          }
                        },
                        "required": [
                          "batch_id",
                          "resolution",
                          "resolved_at",
                          "resolved_by",
                          "document_count",
                          "live_count",
                          "amount_clp"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "batch_id": "01930000-0000-7000-8000-0000000000b9",
                      "resolution": "pagado",
                      "resolved_at": "2026-11-08T14:00:00.000Z",
                      "resolved_by": "00000000-0000-4000-8000-000000000001",
                      "document_count": 3,
                      "live_count": 3,
                      "amount_clp": 908556
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/possible-duplicates": {
      "get": {
        "summary": "Posibles duplicados: bank lines that repeat",
        "description": "Bank lines that repeat under one bank-issued operation id on the same account, day, direction and amount, at least `min_repeats` times (default 3). An id derived from the date (ending in six zeros) is not a bank reference and never counts. READ-ONLY: Vitrina deletes nothing here; someone reviews the list before any change. The Chipax import skips a repeated line of this kind on its next run. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» and no glosa on money in. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 2,
              "maximum": 10,
              "description": "A line counts when it repeats at least this many times (default 3)."
            },
            "required": false,
            "name": "min_repeats",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "min_repeats": {
                          "type": "integer"
                        },
                        "group_count": {
                          "type": "integer"
                        },
                        "line_count": {
                          "type": "integer"
                        },
                        "groups": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "day": {
                                "type": "string"
                              },
                              "amount_clp": {
                                "type": "integer",
                                "description": "Whole CLP."
                              },
                              "direction": {
                                "type": "string",
                                "enum": [
                                  "inbound",
                                  "outbound"
                                ]
                              },
                              "bank_operation": {
                                "type": "string",
                                "description": "The bank's own reference for the operation."
                              },
                              "description": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "repeats": {
                                "type": "integer"
                              },
                              "total_clp": {
                                "type": "integer",
                                "description": "amount × repeats."
                              },
                              "bank_account": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "institution_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "number_masked": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "name",
                                  "institution_name",
                                  "number_masked"
                                ]
                              },
                              "lines": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string"
                                    },
                                    "source": {
                                      "type": "string"
                                    },
                                    "description": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "id",
                                    "source",
                                    "description"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "key",
                              "day",
                              "amount_clp",
                              "direction",
                              "bank_operation",
                              "description",
                              "repeats",
                              "total_clp",
                              "bank_account",
                              "lines"
                            ]
                          }
                        }
                      },
                      "required": [
                        "min_repeats",
                        "group_count",
                        "line_count",
                        "groups"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "min_repeats": 3,
                    "group_count": 1,
                    "line_count": 3,
                    "groups": [
                      {
                        "key": "acct|2025121010599320|2527239|outbound|2025-12-10",
                        "day": "2025-12-10",
                        "amount_clp": 2527239,
                        "direction": "outbound",
                        "bank_operation": "2025121010599320",
                        "description": "Pk smart4d tasa int. 2,09%",
                        "repeats": 3,
                        "total_clp": 7581717,
                        "bank_account": {
                          "id": "01930000-0000-7000-8000-0000000000b2",
                          "name": "Itaú tarjeta de crédito",
                          "institution_name": "Itaú",
                          "number_masked": "•••• 5402"
                        },
                        "lines": [
                          {
                            "id": "01930000-0000-7000-8000-0000000000a1",
                            "source": "chipax",
                            "description": "Pk smart4d tasa int. 2,09%"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/chipax": {
      "get": {
        "summary": "Importar desde Chipax: connection and runs",
        "description": "Whether the workspace stored its Chipax API credentials (only a masked App ID hint, never the secret), the run in progress and the last finished one. Vitrina only READS Chipax: it logs in and sends GET requests; it never creates, edits or deletes anything in the Chipax account. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "Connection and runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasChipaxStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": true,
                    "app_id_preview": "a1b…9f2",
                    "verified_at": "2026-10-30T21:58:40.000Z",
                    "active_import": null,
                    "last_import": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a16",
                      "mode": "dry_run",
                      "status": "done",
                      "from": "2025-01-01",
                      "to": "2026-10-30",
                      "account_map": [],
                      "requested_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                      "error": null,
                      "started_at": "2026-10-30T22:01:04.000Z",
                      "finished_at": "2026-10-30T22:14:51.000Z",
                      "created_at": "2026-10-30T22:01:02.000Z",
                      "report": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/chipax/credentials": {
      "delete": {
        "summary": "Importar desde Chipax: «Eliminar credenciales»",
        "description": "Deletes the stored Chipax credentials. What was already imported stays. Idempotent. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasChipaxStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": false,
                    "app_id_preview": null,
                    "verified_at": null,
                    "active_import": null,
                    "last_import": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a16",
                      "mode": "dry_run",
                      "status": "done",
                      "from": "2025-01-01",
                      "to": "2026-10-30",
                      "account_map": [],
                      "requested_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                      "error": null,
                      "started_at": "2026-10-30T22:01:04.000Z",
                      "finished_at": "2026-10-30T22:14:51.000Z",
                      "created_at": "2026-10-30T22:01:02.000Z",
                      "report": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/finanzas/chipax/imports": {
      "get": {
        "summary": "Importar desde Chipax: the runs",
        "description": "The last 20 runs, newest first (no report; GET one for it). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "Runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/FinanzasChipaxImport"
                          },
                          {
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a16",
                      "mode": "dry_run",
                      "status": "done",
                      "from": "2025-01-01",
                      "to": "2026-10-30",
                      "account_map": [],
                      "requested_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                      "error": null,
                      "started_at": "2026-10-30T22:01:04.000Z",
                      "finished_at": "2026-10-30T22:14:51.000Z",
                      "created_at": "2026-10-30T22:01:02.000Z",
                      "report": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Importar desde Chipax: start a dry run or an import",
        "description": "Queues one run (202); poll `GET /finanzas/chipax/imports/{id}`. Reading Chipax takes minutes (its rate limit: one filtered request every 5 s per endpoint). `dry_run` writes nothing but the report: counts per entity (create / match / already / skip with reasons), the chart mapping with the accounts it cannot decide, and a totals check against Chipax's Resultado Operacional per account and month. `import` does it: idempotent (a re-run changes nothing), documents the SII already gave Vitrina are matched on (issuer RUT, DTE type, folio) and enriched, never duplicated, and every write is automatic (actor `import`), so a closed month accepts it and logs it as an «ajuste posterior al cierre». Pass `account_map` with the person's choices from the dry-run report. One run at a time. Vitrina only READS Chipax: it logs in and sends GET requests; it never creates, edits or deletes anything in the Chipax account. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mode": {
                    "type": "string",
                    "enum": [
                      "dry_run",
                      "import"
                    ],
                    "description": "dry_run = read Chipax and report what an import would do (writes nothing but the report); import = do it. Run a dry run first."
                  },
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "First day of history (default: 1 January of last year)."
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Last day (default: today). At most three years per run."
                  },
                  "account_map": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "chipax_account": {
                          "type": "integer"
                        },
                        "action": {
                          "type": "string",
                          "enum": [
                            "map",
                            "create",
                            "skip"
                          ],
                          "description": "map = file it under an existing Vitrina account (`account_id`); create = recreate it in the plan de cuentas (same name and tree); skip = do not import what Chipax classified there."
                        },
                        "account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "Required with `map`."
                        }
                      },
                      "required": [
                        "chipax_account",
                        "action"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 1000,
                    "description": "The person's choices for Chipax accounts (the dry-run report lists every account with its proposal). Entries win over the proposal and are remembered for later runs."
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              },
              "example": {
                "mode": "import",
                "from": "2025-01-01",
                "to": "2026-10-30",
                "account_map": [
                  {
                    "chipax_account": 5110,
                    "action": "map",
                    "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c12"
                  },
                  {
                    "chipax_account": 5207,
                    "action": "create"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/FinanzasChipaxImport"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a16",
                    "mode": "import",
                    "status": "queued",
                    "from": "2025-01-01",
                    "to": "2026-10-30",
                    "account_map": [
                      {
                        "chipax_account": 5110,
                        "action": "map",
                        "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c12"
                      },
                      {
                        "chipax_account": 5207,
                        "action": "create",
                        "account_id": null
                      }
                    ],
                    "requested_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                    "error": null,
                    "started_at": null,
                    "finished_at": null,
                    "created_at": "2026-10-30T22:01:02.000Z",
                    "report": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A run is in progress, or Chipax is not connected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "CONFLICT",
                    "message": "Ya hay una importación de Chipax en curso. Espera a que termine.",
                    "details": {
                      "reason": "import_running"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/chipax/imports/{id}": {
      "get": {
        "summary": "Importar desde Chipax: one run and its report",
        "description": "The run and its report (partial while `running`; `phase` says where it is). In a dry run the counts say what an import would do. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The run",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/FinanzasChipaxImport"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a16",
                    "mode": "dry_run",
                    "status": "done",
                    "from": "2025-01-01",
                    "to": "2026-10-30",
                    "account_map": [],
                    "requested_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01",
                    "error": null,
                    "started_at": "2026-10-30T22:01:04.000Z",
                    "finished_at": "2026-10-30T22:14:51.000Z",
                    "created_at": "2026-10-30T22:01:02.000Z",
                    "report": {
                      "phase": "done",
                      "dry_run": true,
                      "range": {
                        "from": "2025-01-01",
                        "to": "2026-10-30"
                      },
                      "read": {
                        "accounts": 59,
                        "clients": 3,
                        "compra": 1240,
                        "dte": 18,
                        "honorario": 310,
                        "boleta_tercero": 0,
                        "gasto": 42,
                        "remuneracion": 96,
                        "previred": 22,
                        "impuesto": 22,
                        "cartolas": 2665,
                        "ro_rows": 1950
                      },
                      "accounts": {
                        "create": 4,
                        "match": 54,
                        "already": 0,
                        "skip": 1,
                        "skip_reasons": {
                          "ambiguous": 1
                        },
                        "items": [
                          {
                            "chipax_account": 5101,
                            "name": "Laboratorio",
                            "chipax_path": "Costos › Laboratorio",
                            "kind": "costo",
                            "action": "map",
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c11",
                            "account_path": "Costos de explotación › Laboratorios",
                            "chipax_parent_account": 5100,
                            "basis": "same_name",
                            "candidates": []
                          },
                          {
                            "chipax_account": 5207,
                            "name": "Arriendo bodega",
                            "chipax_path": "Gastos › Arriendo bodega",
                            "kind": "gasto",
                            "action": "create",
                            "account_id": null,
                            "account_path": null,
                            "chipax_parent_account": 5200,
                            "basis": "new",
                            "candidates": []
                          },
                          {
                            "chipax_account": 5110,
                            "name": "Dentistas",
                            "chipax_path": "Costos › Dentistas",
                            "kind": "costo",
                            "action": "skip",
                            "account_id": null,
                            "account_path": null,
                            "chipax_parent_account": 5100,
                            "basis": "ambiguous",
                            "candidates": [
                              {
                                "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c12",
                                "path": "Costos de explotación › Dentistas"
                              },
                              {
                                "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c13",
                                "path": "Costos de explotación › Profesionales › Dentistas"
                              }
                            ]
                          }
                        ]
                      },
                      "contrapartes": {
                        "create": 37,
                        "match": 112,
                        "already": 0,
                        "skip": 0,
                        "skip_reasons": {}
                      },
                      "documents": {
                        "create": 180,
                        "match": 1530,
                        "already": 0,
                        "skip": 0,
                        "skip_reasons": {},
                        "by_model": {
                          "compra": 1240,
                          "honorario": 310,
                          "gasto": 42,
                          "remuneracion": 96,
                          "previred": 22,
                          "impuesto": 22,
                          "dte": 18
                        }
                      },
                      "classifications": {
                        "create": 1650,
                        "match": 0,
                        "already": 0,
                        "skip": 42,
                        "skip_reasons": {
                          "classified_in_vitrina": 12,
                          "unmapped_account": 30
                        },
                        "scaled": 1180
                      },
                      "links": {
                        "create": 2210,
                        "match": 0,
                        "already": 0,
                        "skip": 310,
                        "skip_reasons": {
                          "movement_not_found": 290,
                          "movement_ambiguous": 20
                        }
                      },
                      "movement_classifications": {
                        "create": 64,
                        "match": 0,
                        "already": 0,
                        "skip": 0,
                        "skip_reasons": {}
                      },
                      "rules": {
                        "create": 41,
                        "match": 2,
                        "already": 0,
                        "skip": 0,
                        "skip_reasons": {},
                        "items": [
                          {
                            "rut": "76086428-5",
                            "name": "Laboratorio Dental Andes Ltda",
                            "document_kind": "purchase",
                            "chipax_account": 5101,
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c11",
                            "documents": 48
                          }
                        ]
                      },
                      "payment_terms": {
                        "create": 9,
                        "match": 1,
                        "already": 0,
                        "skip": 0,
                        "skip_reasons": {},
                        "items": [
                          {
                            "rut": "76086428-5",
                            "days": 30,
                            "documents": 48
                          }
                        ]
                      },
                      "aliases": {
                        "create": 6,
                        "match": 0,
                        "already": 0,
                        "skip": 1,
                        "skip_reasons": {
                          "rut_is_another_contraparte": 1
                        }
                      },
                      "unmapped": [
                        {
                          "type": "account",
                          "chipax_ref": "5110",
                          "label": "Costos › Dentistas",
                          "reason": "ambiguous"
                        },
                        {
                          "type": "link",
                          "chipax_ref": "88123:compra:44120",
                          "label": "2025-03-04 cargo $410000",
                          "reason": "movement_not_found"
                        }
                      ],
                      "totals_check": {
                        "available": true,
                        "mismatches": 1,
                        "explained": 1,
                        "explained_by_code": {
                          "redondeo": -1
                        },
                        "rows": [
                          {
                            "chipax_account": 5110,
                            "account_id": null,
                            "month": "2026-09",
                            "chipax_clp": 6120000,
                            "imported_chipax_clp": 0,
                            "vitrina_clp": 0,
                            "difference_clp": -6120000,
                            "explanations": [],
                            "unexplained_clp": -6120000,
                            "status": "mismatch",
                            "matches": false
                          },
                          {
                            "chipax_account": 5101,
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4c11",
                            "month": "2026-09",
                            "chipax_clp": 2380000,
                            "imported_chipax_clp": 2380000,
                            "vitrina_clp": 2379999,
                            "difference_clp": -1,
                            "explanations": [
                              {
                                "code": "redondeo",
                                "amount_clp": -1
                              }
                            ],
                            "unexplained_clp": 0,
                            "status": "explained",
                            "matches": true
                          }
                        ]
                      },
                      "not_in_api": [
                        "Las automatizaciones (reglas) de Chipax no están en su API: se infieren del historial (un proveedor que siempre fue a la misma cuenta)."
                      ],
                      "warnings": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "NOT_FOUND",
                    "message": "Importación de Chipax no encontrada"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/ai/settings": {
      "get": {
        "summary": "Read the IA switch (Aplicar sugerencias de IA automáticamente)",
        "description": "After every bank, Mercado Pago or SII sync, the IA looks only at what the engine, the rules and the deterministic proposals left open, and never at patient data. A confident answer is applied on its own (marked IA, undoable in Hecho por Vitrina; an undo teaches it); otherwise it becomes the Por resolver proposal (reason `ia`). `auto_apply` is on by default. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The switch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAiSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "auto_apply": true,
                    "updated_at": null,
                    "updated_by": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Turn automatic IA suggestions on or off",
        "description": "After every bank, Mercado Pago or SII sync, the IA looks only at what the engine, the rules and the deterministic proposals left open, and never at patient data. A confident answer is applied on its own (marked IA, undoable in Hecho por Vitrina; an undo teaches it); otherwise it becomes the Por resolver proposal (reason `ia`). With `auto_apply: false` the IA keeps answering, but its answers only become Por resolver proposals a person confirms. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "auto_apply": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "auto_apply"
                ],
                "additionalProperties": false
              },
              "example": {
                "auto_apply": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAiSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "auto_apply": false,
                    "updated_at": "2026-10-30T21:10:00.000Z",
                    "updated_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4f01"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/ai/metrics": {
      "get": {
        "summary": "IA metrics of a month: calls, auto-applied, undo rate",
        "description": "IA calls in the month (one open item each), how many were applied on their own, proposed or left without an answer, and how many of the applied ones a person undid since (`undo_rate` = undone / auto_applied, null when none were applied). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM` (Santiago calendar)."
            },
            "required": true,
            "name": "month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Metrics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAiMetrics"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "calls": 42,
                    "auto_applied": 18,
                    "proposed": 15,
                    "no_answer": 9,
                    "undone": 1,
                    "undo_rate": 0.0556,
                    "input_tokens": 37800
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/resultado": {
      "get": {
        "summary": "Resultado: the operating result by month",
        "description": "Reportes › Resultado on the plan de cuentas, one column per month (`months`) plus a total. Income = the boletas and facturas de venta ISSUED in the month, by issue date, paid or not. Every amount is NET: a sale at neto + exento (IVA débito is not income), a purchase without its recoverable IVA crédito fiscal and specific taxes (IVA no recuperable is a cost; a boleta or a document with no IVA breakdown counts in full); classification lines are prorated to net; movements count in full. «Por conciliar» on the `ingresos` row is the part not yet matched to money received, at document totals. A classified item counts in the accrual month of each classification line; an unclassified document in its issue month («Ingresos / Egresos sin clasificar»). Notas de crédito subtract; boletas de honorarios count gross (the retención is a tax owed, not a smaller cost). «Egresos sin documento» (classified money out with no legal document) subtracts in the resultado operacional; «Retiros» sit below it, outside the result. A closed month carries `post_close` («ajuste posterior al cierre»). The current month is `proforma`. Default: January of the current year to the current month; at most 24 months. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of `to`’s year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: the current month). At most 24 months."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The resultado",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasResultado"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-09",
                    "to": "2026-10",
                    "current_month": "2026-10",
                    "currency": "CLP",
                    "months": [
                      {
                        "month": "2026-09",
                        "label": "septiembre 2026",
                        "proforma": false,
                        "closed": true,
                        "post_close": {
                          "closed_at": "2026-10-03T14:20:00.000Z",
                          "adjustment_count": 1,
                          "changed": true,
                          "result_delta_clp": -180000,
                          "accounts": [
                            {
                              "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5104",
                              "name": "Laboratorios",
                              "path": "Costos de explotación › Laboratorios",
                              "at_close_clp": 2380000,
                              "now_clp": 2560000,
                              "delta_clp": 180000
                            }
                          ]
                        }
                      },
                      {
                        "month": "2026-10",
                        "label": "octubre 2026",
                        "proforma": true,
                        "closed": false,
                        "post_close": null
                      }
                    ],
                    "rows": [
                      {
                        "key": "ingresos",
                        "type": "section",
                        "label": "Ingresos",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": 1,
                        "values": [
                          31250000,
                          18400000
                        ],
                        "total": 49650000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": {
                          "values": [
                            1200000,
                            6400000
                          ],
                          "total": 7600000,
                          "counts": [
                            3,
                            41
                          ],
                          "count_total": 44
                        },
                        "undocumented": null,
                        "by_account": null,
                        "drillable": false
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5101",
                        "type": "account",
                        "label": "Ingresos de explotación",
                        "parent_key": "ingresos",
                        "depth": 2,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5101",
                          "kind": "ingreso",
                          "path": "Ingresos › Ingresos de explotación",
                          "archived": false,
                          "has_children": true
                        },
                        "sign": 1,
                        "values": [
                          30800000,
                          17900000
                        ],
                        "total": 48700000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5102",
                        "type": "account",
                        "label": "Atención odontológica",
                        "parent_key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5101",
                        "depth": 3,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5102",
                          "kind": "ingreso",
                          "path": "Ingresos › Ingresos de explotación › Atención odontológica",
                          "archived": false,
                          "has_children": false
                        },
                        "sign": 1,
                        "values": [
                          30800000,
                          17900000
                        ],
                        "total": 48700000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "ingresos_sin_clasificar",
                        "type": "line",
                        "label": "Ingresos sin clasificar",
                        "parent_key": "ingresos",
                        "depth": 1,
                        "account": null,
                        "sign": 1,
                        "values": [
                          450000,
                          500000
                        ],
                        "total": 950000,
                        "counts": [
                          2,
                          3
                        ],
                        "count_total": 5,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "costos",
                        "type": "section",
                        "label": "Costos",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": -1,
                        "values": [
                          9840000,
                          5100000
                        ],
                        "total": 14940000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": false
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5103",
                        "type": "account",
                        "label": "Costos de explotación",
                        "parent_key": "costos",
                        "depth": 1,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5103",
                          "kind": "costo",
                          "path": "Costos de explotación",
                          "archived": false,
                          "has_children": true
                        },
                        "sign": -1,
                        "values": [
                          9840000,
                          5100000
                        ],
                        "total": 14940000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5104",
                        "type": "account",
                        "label": "Laboratorios",
                        "parent_key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5103",
                        "depth": 2,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5104",
                          "kind": "costo",
                          "path": "Costos de explotación › Laboratorios",
                          "archived": false,
                          "has_children": false
                        },
                        "sign": -1,
                        "values": [
                          2560000,
                          1300000
                        ],
                        "total": 3860000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "margen_bruto",
                        "type": "subtotal",
                        "label": "Margen bruto",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": 0,
                        "values": [
                          21410000,
                          13300000
                        ],
                        "total": 34710000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": [
                          68.5,
                          72.3
                        ],
                        "margin_pct_total": 69.8,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": false
                      },
                      {
                        "key": "gastos",
                        "type": "section",
                        "label": "Gastos",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": -1,
                        "values": [
                          6200000,
                          4100000
                        ],
                        "total": 10300000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": false
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5105",
                        "type": "account",
                        "label": "Gastos de administración y venta",
                        "parent_key": "gastos",
                        "depth": 1,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5105",
                          "kind": "gasto",
                          "path": "Gastos de administración y venta",
                          "archived": false,
                          "has_children": true
                        },
                        "sign": -1,
                        "values": [
                          6200000,
                          4100000
                        ],
                        "total": 10300000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5106",
                        "type": "account",
                        "label": "Arriendo",
                        "parent_key": "account:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5105",
                        "depth": 3,
                        "account": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5106",
                          "kind": "gasto",
                          "path": "Gastos de administración y venta › Gastos generales › Arriendo",
                          "archived": false,
                          "has_children": false
                        },
                        "sign": -1,
                        "values": [
                          2400000,
                          2400000
                        ],
                        "total": 4800000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "egresos_sin_documento",
                        "type": "line",
                        "label": "Egresos sin documento",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": -1,
                        "values": [
                          620000,
                          380000
                        ],
                        "total": 1000000,
                        "counts": [
                          7,
                          4
                        ],
                        "count_total": 11,
                        "alert": true,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": [
                          {
                            "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5107",
                            "name": "Mantenimientos (equipos y computación)",
                            "path": "Gastos de administración y venta › Gastos generales › Mantenimientos (equipos y computación)",
                            "kind": "gasto",
                            "total": 1000000
                          }
                        ],
                        "drillable": true
                      },
                      {
                        "key": "egresos_sin_clasificar",
                        "type": "line",
                        "label": "Egresos sin clasificar",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": -1,
                        "values": [
                          0,
                          310000
                        ],
                        "total": 310000,
                        "counts": [
                          0,
                          2
                        ],
                        "count_total": 2,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": true
                      },
                      {
                        "key": "resultado_operacional",
                        "type": "subtotal",
                        "label": "Resultado operacional",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": 0,
                        "values": [
                          14590000,
                          8510000
                        ],
                        "total": 23100000,
                        "counts": null,
                        "count_total": null,
                        "alert": false,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": null,
                        "by_account": null,
                        "drillable": false
                      },
                      {
                        "key": "retiros",
                        "type": "line",
                        "label": "Retiros",
                        "parent_key": null,
                        "depth": 0,
                        "account": null,
                        "sign": 0,
                        "values": [
                          3000000,
                          3000000
                        ],
                        "total": 6000000,
                        "counts": [
                          1,
                          1
                        ],
                        "count_total": 2,
                        "alert": true,
                        "margin_pct": null,
                        "margin_pct_total": null,
                        "por_conciliar": null,
                        "undocumented": {
                          "values": [
                            0,
                            3000000
                          ],
                          "total": 3000000,
                          "counts": [
                            0,
                            1
                          ],
                          "count_total": 1
                        },
                        "by_account": null,
                        "drillable": true
                      }
                    ],
                    "summary": {
                      "resultado_operacional_clp": 23100000,
                      "ingresos_clp": 49650000,
                      "por_conciliar_clp": 7600000,
                      "egresos_sin_documento": {
                        "count": 11,
                        "total_clp": 1000000
                      },
                      "retiros_clp": 6000000,
                      "retiros_sin_documento": {
                        "count": 1,
                        "total_clp": 3000000
                      }
                    },
                    "waterfall": [
                      {
                        "key": "ingresos",
                        "label": "Ingresos",
                        "amount_clp": 49650000,
                        "running_clp": 49650000
                      },
                      {
                        "key": "costos",
                        "label": "Costos",
                        "amount_clp": -14940000,
                        "running_clp": 34710000
                      },
                      {
                        "key": "gastos",
                        "label": "Gastos",
                        "amount_clp": -10300000,
                        "running_clp": 24410000
                      },
                      {
                        "key": "egresos_sin_documento",
                        "label": "Egresos sin documento",
                        "amount_clp": -1000000,
                        "running_clp": 23410000
                      },
                      {
                        "key": "egresos_sin_clasificar",
                        "label": "Egresos sin clasificar",
                        "amount_clp": -310000,
                        "running_clp": 23100000
                      },
                      {
                        "key": "resultado_operacional",
                        "label": "Resultado operacional",
                        "amount_clp": 23100000,
                        "running_clp": 23100000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/resultado/items": {
      "get": {
        "summary": "Resultado: the documents and movements behind a line",
        "description": "The drill-down of one row (`row`; `account` needs `account_id`, and a group account includes every account under it) for one month (`month`, a cell) or a range (`from`/`to`, the total column). One item per document or movement, with its signed contribution to the line (`amount_clp`, net) and at totals (`gross_amount_clp`), newest first; `sum_clp` equals the cell. A document carries `result_clp`, what it counts for in the resultado, next to its `total_clp`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "account",
                "ingresos_sin_clasificar",
                "egresos_sin_documento",
                "egresos_sin_clasificar",
                "retiros",
                "por_conciliar"
              ],
              "description": "Which line to open: `account` (needs `account_id`; a group includes every account under it), `ingresos_sin_clasificar`, `egresos_sin_documento` (optional `account_id` narrows it), `egresos_sin_clasificar`, `retiros`, `por_conciliar`."
            },
            "required": true,
            "name": "row",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "account_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "One month (the cell). Overrides `from`/`to`."
            },
            "required": false,
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month, `YYYY-MM`."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "A month, `YYYY-MM`."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasResultadoItems"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "row": "egresos_sin_documento",
                    "account": null,
                    "from": "2026-10",
                    "to": "2026-10",
                    "total": 1,
                    "sum_clp": 380000,
                    "gross_sum_clp": 380000,
                    "items": [
                      {
                        "type": "movement",
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5201",
                        "day": "2026-10-14",
                        "periods": [
                          "2026-10"
                        ],
                        "amount_clp": 380000,
                        "gross_amount_clp": 380000,
                        "counterparty_name": "Juan Soto",
                        "description": "Jardín octubre",
                        "account_ids": [
                          "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5107"
                        ],
                        "accounts": [
                          {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5107",
                            "name": "Mantenimientos (equipos y computación)",
                            "path": "Gastos de administración y venta › Gastos generales › Mantenimientos (equipos y computación)"
                          }
                        ],
                        "documented": false,
                        "accepted_without_document": true,
                        "document": null,
                        "movement": {
                          "direction": "outbound",
                          "amount_clp": 380000
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/resultado/export": {
      "get": {
        "summary": "Export the resultado (xlsx / csv)",
        "description": "The resultado as a spreadsheet: sheet «Resultado», exactly the rows and months the screen shows (plus «Por conciliar», «Margen bruto %», «Retiros sin documento» and one line per month with an «ajuste posterior al cierre»), and sheet «Detalle», every document and movement behind each line with its net («En el resultado») and its total (xlsx only; csv is the first sheet). Same query as the resultado; `format` `xlsx` (default) or `csv`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:export`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of `to`’s year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: the current month). At most 24 months."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "xlsx",
                "csv"
              ],
              "description": "`xlsx` (default) or `csv`."
            },
            "required": false,
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The resultado, as an attachment",
            "content": {
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow": {
      "get": {
        "summary": "Flujo de caja: actual, projection, band and lowest point",
        "description": "Per day (or ISO week): what came in and went out (actual, before today) and what is expected (projected, today on), with the closing balance of each period. The projection starts from today's balance of the operating accounts (every bank account except savings accounts) and adds known items on their dates: open payables and receivables by due date (outstanding = total − retención − live reconciliation links), the retención of honorarios received (due with the F29 on the 20th of the next month), recurring items, patient cuotas, booked appointments with a price, the efectivo por depositar (`cash_pending`, spread over its expected deposit days) and planned rescues. That is the CONFIRMED balance (`closing_balance_clp`), the one every headline reads. A payable past its due date with no matching payment in the data is a «Vencida sin pago visto»: it stays out (`overdue_no_payment_seen`, the list at GET /finanzas/cash-flow/overdue-no-payment-seen) unless it was given a date with «Mover pagos»; overdue receivables stay out too (`overdue`). A document «Pagado antes de Vitrina» is settled and never projected (`inherited_overdue` is retired, always zero). The lowest point (`lowest`) includes today's balance and is never above it (`lowest.is_today`). `expected_balance_clp` («con cobros esperados», the dashed line) adds, from tomorrow, each day's typical income: the median of that weekday's bank income over the last 90 days, counted only above the day's known income. `band` is retired and always null. Past days are actual bank movements; transfers between operating accounts are neither in nor out. `lowest` is the lowest confirmed balance in the range (its `cause` = the payment at which the confirmed balance first goes below zero, else the biggest payment of that day); `month_end` is the projected balance at the end of the current month («¿cuánto me queda a fin de mes?»), computed even when `to` is earlier. Range: up to a year back and a year ahead, at most 366 days. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day (default: 30 days ago). Up to a year back."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day (default: `horizon_days` ahead, 30). Up to a year ahead."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "description": "The horizon in days from today (default 30): the window the page shows and the one the «Mover pagos» / «Rescatar fondo» previews measure `lowest` over. Pass the same value to both."
            },
            "required": false,
            "name": "horizon_days",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week"
              ],
              "description": "Periods of one day (default) or one ISO week."
            },
            "required": false,
            "name": "granularity",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The cash flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlow"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-10-30",
                    "from": "2026-10-29",
                    "to": "2026-11-01",
                    "granularity": "day",
                    "opening": {
                      "balance_clp": 4917109,
                      "as_of": "2026-10-30T08:00:00.000Z",
                      "accounts_count": 4,
                      "unknown_count": 0,
                      "savings": [
                        {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5a02",
                          "name": "Cuenta de ahorro",
                          "institution_name": "Banco Estado",
                          "balance_clp": 1200000
                        }
                      ],
                      "savings_clp": 1200000
                    },
                    "periods": [
                      {
                        "start": "2026-10-29",
                        "end": "2026-10-29",
                        "kind": "actual",
                        "inflow_clp": 2412000,
                        "typical_inflow_clp": 0,
                        "outflow_clp": 698400,
                        "closing_balance_clp": 4617109,
                        "expected_balance_clp": 4617109,
                        "band": null,
                        "items_count": 0
                      },
                      {
                        "start": "2026-10-30",
                        "end": "2026-10-30",
                        "kind": "projected",
                        "inflow_clp": 120000,
                        "typical_inflow_clp": 0,
                        "outflow_clp": 412690,
                        "closing_balance_clp": 4624419,
                        "expected_balance_clp": 4624419,
                        "band": null,
                        "items_count": 2
                      },
                      {
                        "start": "2026-10-31",
                        "end": "2026-10-31",
                        "kind": "projected",
                        "inflow_clp": 0,
                        "typical_inflow_clp": 1150000,
                        "outflow_clp": 0,
                        "closing_balance_clp": 4624419,
                        "expected_balance_clp": 5774419,
                        "band": null,
                        "items_count": 0
                      },
                      {
                        "start": "2026-11-01",
                        "end": "2026-11-01",
                        "kind": "projected",
                        "inflow_clp": 0,
                        "typical_inflow_clp": 0,
                        "outflow_clp": 0,
                        "closing_balance_clp": 4624419,
                        "expected_balance_clp": 5774419,
                        "band": null,
                        "items_count": 0
                      }
                    ],
                    "lowest": {
                      "date": "2026-10-30",
                      "balance_clp": 4624419,
                      "negative": false,
                      "is_today": false,
                      "cause": null,
                      "cause_balance_after_clp": null
                    },
                    "month_end": {
                      "date": "2026-11-30",
                      "balance_clp": 2310000,
                      "expected_balance_clp": 9410000,
                      "band": null
                    },
                    "totals": {
                      "actual_inflow_clp": 2412000,
                      "actual_outflow_clp": 698400,
                      "projected_inflow_clp": 120000,
                      "typical_inflow_clp": 1150000,
                      "projected_outflow_clp": 412690,
                      "by_source": {
                        "payable": 412690,
                        "receivable": 0,
                        "retention": 0,
                        "recurring": 0,
                        "cuotas": 0,
                        "appointments": 0,
                        "cash_pending": 120000,
                        "rescue": 0
                      }
                    },
                    "overdue": {
                      "payables_clp": 412690,
                      "payables_count": 1,
                      "stale_payables_clp": 0,
                      "stale_payables_count": 0,
                      "receivables_clp": 0,
                      "receivables_count": 0
                    },
                    "overdue_no_payment_seen": {
                      "count": 1,
                      "amount_clp": 412690
                    },
                    "inherited_overdue": {
                      "count": 0,
                      "amount_clp": 0
                    },
                    "cash_pending": {
                      "amount_clp": 11648107,
                      "window_days": 60,
                      "expected_until": "2026-11-06"
                    },
                    "typical_income": {
                      "basis": "history",
                      "weeks": 12
                    },
                    "band_basis": {
                      "from": "2026-08-01",
                      "to": "2026-10-29",
                      "weekdays": [
                        {
                          "weekday": 0,
                          "min_clp": 0,
                          "median_clp": 0,
                          "max_clp": 0
                        },
                        {
                          "weekday": 1,
                          "min_clp": 1720000,
                          "median_clp": 2380000,
                          "max_clp": 3010000
                        },
                        {
                          "weekday": 2,
                          "min_clp": 1640000,
                          "median_clp": 2350000,
                          "max_clp": 2960000
                        },
                        {
                          "weekday": 3,
                          "min_clp": 1810000,
                          "median_clp": 2400000,
                          "max_clp": 3120000
                        },
                        {
                          "weekday": 4,
                          "min_clp": 1700000,
                          "median_clp": 2390000,
                          "max_clp": 2990000
                        },
                        {
                          "weekday": 5,
                          "min_clp": 1590000,
                          "median_clp": 2310000,
                          "max_clp": 3200000
                        },
                        {
                          "weekday": 6,
                          "min_clp": 670000,
                          "median_clp": 1150000,
                          "max_clp": 1480000
                        }
                      ]
                    },
                    "rescues": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7d01",
                        "planned_on": "2026-11-25",
                        "amount_clp": 5100000,
                        "source_name": "Fondo mutuo Itaú",
                        "source_bank_account_id": null,
                        "note": null,
                        "created_at": "2026-10-30T15:00:00.000Z"
                      }
                    ],
                    "moves_count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/calendar": {
      "get": {
        "summary": "Payments calendar: what is due each day, balance after each",
        "description": "The projected items from today, grouped by day, each with the expected balance right after it (money in first, then the biggest payments). The day's typical income lands after its items. `direction` keeps only money in or only payments (balances still count everything). The projection starts from today's balance of the operating accounts (every bank account except savings accounts) and adds known items on their dates: open payables and receivables by due date (outstanding = total − retención − live reconciliation links), the retención of honorarios received (due with the F29 on the 20th of the next month), recurring items, patient cuotas, booked appointments with a price, the efectivo por depositar (`cash_pending`, spread over its expected deposit days) and planned rescues. That is the CONFIRMED balance (`closing_balance_clp`), the one every headline reads. A payable past its due date with no matching payment in the data is a «Vencida sin pago visto»: it stays out (`overdue_no_payment_seen`, the list at GET /finanzas/cash-flow/overdue-no-payment-seen) unless it was given a date with «Mover pagos»; overdue receivables stay out too (`overdue`). A document «Pagado antes de Vitrina» is settled and never projected (`inherited_overdue` is retired, always zero). The lowest point (`lowest`) includes today's balance and is never above it (`lowest.is_today`). `expected_balance_clp` («con cobros esperados», the dashed line) adds, from tomorrow, each day's typical income: the median of that weekday's bank income over the last 90 days, counted only above the day's known income. `band` is retired and always null. Past days are actual bank movements; transfers between operating accounts are neither in nor out. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day (default and minimum: today)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day (default: 30 days ahead). Up to a year ahead."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "inflow",
                "outflow"
              ],
              "description": "Only money in or only payments."
            },
            "required": false,
            "name": "direction",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The calendar",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowCalendar"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-10-30",
                    "from": "2026-10-30",
                    "to": "2026-11-30",
                    "opening_balance_clp": 4917109,
                    "days": [
                      {
                        "date": "2026-11-26",
                        "opening_balance_clp": 15752058,
                        "items": [
                          {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null,
                            "balance_after_clp": -1840000
                          }
                        ],
                        "typical_inflow_clp": 0,
                        "closing_balance_clp": -1840000
                      },
                      {
                        "date": "2026-12-01",
                        "opening_balance_clp": 2310000,
                        "items": [
                          {
                            "key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                            "source": "payable",
                            "direction": "outflow",
                            "label": "Factura 18377 · Laboratorio Dental Andes Ltda",
                            "amount_clp": 362600,
                            "date": "2026-12-01",
                            "due_date": "2026-11-13",
                            "overdue": false,
                            "movable": true,
                            "move": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7c01",
                              "original_on": "2026-11-13",
                              "note": null
                            },
                            "document": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                              "kind": "purchase",
                              "dte_type": 33,
                              "folio": "18377"
                            },
                            "contraparte": {
                              "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6a01",
                              "name": "Laboratorio Dental Andes Ltda"
                            },
                            "recurring_item_id": null,
                            "rescue_id": null,
                            "count": null,
                            "balance_after_clp": 1947400
                          }
                        ],
                        "typical_inflow_clp": 2380000,
                        "closing_balance_clp": 4327400
                      }
                    ],
                    "lowest": {
                      "date": "2026-11-26",
                      "balance_clp": -1840000,
                      "negative": true,
                      "is_today": false,
                      "cause": {
                        "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                        "source": "recurring",
                        "direction": "outflow",
                        "label": "Sueldos",
                        "amount_clp": 17592058,
                        "date": "2026-11-26",
                        "due_date": "2026-11-26",
                        "overdue": false,
                        "movable": true,
                        "move": null,
                        "document": null,
                        "contraparte": null,
                        "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                        "rescue_id": null,
                        "count": null
                      },
                      "cause_balance_after_clp": -1840000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/moves": {
      "post": {
        "summary": "«Mover pagos»: change the planned date of a payment",
        "description": "Re-dates one movable projected item (`key` from the calendar: a document, a recurring occurrence or a retención) to `planned_on` (today to a year ahead). Moving it to its own date removes the move. It changes the plan only: a document keeps its due date and no bank is touched. Answers the lowest point and month-end balance before and after. `?dry_run=true` answers the effect and saves nothing. An item the projection no longer holds (paid, ended) answers 404. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`true` answers the effect without saving anything (to preview «Mover pagos» / «Rescatar fondo»)."
            },
            "required": false,
            "name": "dry_run",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "description": "The horizon in days from today (default 30): the window the page shows and the one the «Mover pagos» / «Rescatar fondo» previews measure `lowest` over. Pass the same value to both."
            },
            "required": false,
            "name": "horizon_days",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasCashFlowMoveBody"
              },
              "example": {
                "item_key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                "planned_on": "2026-12-01",
                "note": "Pagamos con el abono del convenio"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Moved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowMoveResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": false,
                    "move": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7c01",
                      "item_key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                      "original_on": "2026-11-13",
                      "planned_on": "2026-12-01",
                      "note": "Pagamos con el abono del convenio",
                      "updated_at": "2026-10-30T15:10:00.000Z"
                    },
                    "item": {
                      "key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                      "label": "Factura 18377 · Laboratorio Dental Andes Ltda",
                      "amount_clp": 362600,
                      "due_date": "2026-11-13",
                      "planned_on": "2026-12-01"
                    },
                    "effect": {
                      "before": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": -1840000,
                          "negative": true,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 2310000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "after": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": 3260000,
                          "negative": false,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 7410000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "horizon": {
                        "days": 30,
                        "to": "2026-11-30"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/moves/{id}": {
      "delete": {
        "summary": "Undo a moved payment",
        "description": "The item goes back to its own date. It changes the plan only: a document keeps its due date and no bank is touched. Answers the effect. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "description": "The horizon in days from today (default 30): the window the page shows and the one the «Mover pagos» / «Rescatar fondo» previews measure `lowest` over. Pass the same value to both."
            },
            "required": false,
            "name": "horizon_days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Undone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowMoveRemoved"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "removed": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7c01",
                      "item_key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                      "original_on": "2026-11-13",
                      "planned_on": "2026-12-01",
                      "note": "Pagamos con el abono del convenio",
                      "updated_at": "2026-10-30T15:10:00.000Z"
                    },
                    "effect": {
                      "before": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": 3260000,
                          "negative": false,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 7410000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "after": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": -1840000,
                          "negative": true,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 2310000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "horizon": {
                        "days": 30,
                        "to": "2026-11-30"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/rescues": {
      "post": {
        "summary": "«Rescatar fondo»: plan a transfer in from savings or an investment",
        "description": "Adds a planned inflow on `planned_on` (today to a year ahead). Name its source (`source_name`, e.g. «Fondo mutuo Itaú») or pick a savings account (`source_bank_account_id`, `type: cuenta_ahorro`; its name is the default source). An operating account answers 400: its money is already in the balance. It changes the plan only: a document keeps its due date and no bank is touched. Answers the effect; `?dry_run=true` saves nothing (200 instead of 201). Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`true` answers the effect without saving anything (to preview «Mover pagos» / «Rescatar fondo»)."
            },
            "required": false,
            "name": "dry_run",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "description": "The horizon in days from today (default 30): the window the page shows and the one the «Mover pagos» / «Rescatar fondo» previews measure `lowest` over. Pass the same value to both."
            },
            "required": false,
            "name": "horizon_days",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasCashFlowRescueBody"
              },
              "example": {
                "planned_on": "2026-11-25",
                "amount_clp": 5100000,
                "source_name": "Fondo mutuo Itaú"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Effect only (dry run)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowRescueResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": true,
                    "rescue": null,
                    "effect": {
                      "before": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": -1840000,
                          "negative": true,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 2310000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "after": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": 3260000,
                          "negative": false,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 7410000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "horizon": {
                        "days": 30,
                        "to": "2026-11-30"
                      }
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Planned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowRescueResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": false,
                    "rescue": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7d01",
                      "planned_on": "2026-11-25",
                      "amount_clp": 5100000,
                      "source_name": "Fondo mutuo Itaú",
                      "source_bank_account_id": null,
                      "note": null,
                      "created_at": "2026-10-30T15:00:00.000Z"
                    },
                    "effect": {
                      "before": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": -1840000,
                          "negative": true,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 2310000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "after": {
                        "lowest": {
                          "date": "2026-11-26",
                          "balance_clp": 3260000,
                          "negative": false,
                          "is_today": false,
                          "cause": {
                            "key": "recurring:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01:2026-11-26",
                            "source": "recurring",
                            "direction": "outflow",
                            "label": "Sueldos",
                            "amount_clp": 17592058,
                            "date": "2026-11-26",
                            "due_date": "2026-11-26",
                            "overdue": false,
                            "movable": true,
                            "move": null,
                            "document": null,
                            "contraparte": null,
                            "recurring_item_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                            "rescue_id": null,
                            "count": null
                          },
                          "cause_balance_after_clp": -1840000
                        },
                        "month_end": {
                          "date": "2026-11-30",
                          "balance_clp": 7410000,
                          "expected_balance_clp": 9410000,
                          "band": null
                        }
                      },
                      "horizon": {
                        "days": 30,
                        "to": "2026-11-30"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/rescues/{id}": {
      "delete": {
        "summary": "Drop a planned rescue",
        "description": "Removes a planned «Rescatar fondo». It changes the plan only: a document keeps its due date and no bank is touched. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Dropped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowRescueRemoved"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "removed": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7d01",
                      "planned_on": "2026-11-25",
                      "amount_clp": 5100000,
                      "source_name": "Fondo mutuo Itaú",
                      "source_bank_account_id": null,
                      "note": null,
                      "created_at": "2026-10-30T15:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/recurring-items": {
      "get": {
        "summary": "Recurring items of the cash flow",
        "description": "Declared recurring inflows and outflows (rent, payroll, a credit instalment, taxes) with their next occurrence; ended ones last. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The recurring items",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FinanzasRecurringItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                      "name": "Sueldos",
                      "direction": "outflow",
                      "amount_clp": 17592058,
                      "cadence": "monthly",
                      "starts_on": "2026-01-26",
                      "ends_on": null,
                      "account": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d20",
                        "name": "Sueldos",
                        "kind": "gasto",
                        "archived": false
                      },
                      "contraparte": null,
                      "note": null,
                      "summary": "Mensual · $17.592.058",
                      "status": "active",
                      "next_on": "2026-11-26",
                      "created_at": "2026-10-30T14:00:00.000Z",
                      "updated_at": "2026-10-30T14:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Declare a recurring item",
        "description": "Amount (whole CLP), cadence, plan de cuentas account (not archived), first day and optional end. With `contraparte_id`, an occurrence is skipped when a document of that Contraparte falls due within half a period of it (the document stands for it). Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasRecurringItemBody"
              },
              "example": {
                "name": "Sueldos",
                "direction": "outflow",
                "amount_clp": 17592058,
                "cadence": "monthly",
                "starts_on": "2026-01-26",
                "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d20"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRecurringItem"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                    "name": "Sueldos",
                    "direction": "outflow",
                    "amount_clp": 17592058,
                    "cadence": "monthly",
                    "starts_on": "2026-01-26",
                    "ends_on": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d20",
                      "name": "Sueldos",
                      "kind": "gasto",
                      "archived": false
                    },
                    "contraparte": null,
                    "note": null,
                    "summary": "Mensual · $17.592.058",
                    "status": "active",
                    "next_on": "2026-11-26",
                    "created_at": "2026-10-30T14:00:00.000Z",
                    "updated_at": "2026-10-30T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/cash-flow/recurring-items/{id}": {
      "patch": {
        "summary": "Edit a recurring item",
        "description": "Send only what changes. The whole series follows (past occurrences are not projected anyway); `ends_on: null` reopens an ended item. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasRecurringItemPatch"
              },
              "example": {
                "amount_clp": 18100000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRecurringItem"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                    "name": "Sueldos",
                    "direction": "outflow",
                    "amount_clp": 18100000,
                    "cadence": "monthly",
                    "starts_on": "2026-01-26",
                    "ends_on": null,
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d20",
                      "name": "Sueldos",
                      "kind": "gasto",
                      "archived": false
                    },
                    "contraparte": null,
                    "note": null,
                    "summary": "Mensual · $18.100.000",
                    "status": "active",
                    "next_on": "2026-11-26",
                    "created_at": "2026-10-30T14:00:00.000Z",
                    "updated_at": "2026-10-30T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/recurring-items/{id}/end": {
      "post": {
        "summary": "«Terminar» a recurring item",
        "description": "No occurrence after `ends_on` (default: today; the day before it starts when it has not started). Nothing is deleted. An item that already ended answers 409. Audited. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasRecurringItemEndBody"
              },
              "example": {
                "ends_on": "2026-12-31"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ended",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasRecurringItem"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7b01",
                    "name": "Sueldos",
                    "direction": "outflow",
                    "amount_clp": 17592058,
                    "cadence": "monthly",
                    "starts_on": "2026-01-26",
                    "ends_on": "2026-12-31",
                    "account": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f4d20",
                      "name": "Sueldos",
                      "kind": "gasto",
                      "archived": false
                    },
                    "contraparte": null,
                    "note": null,
                    "summary": "Mensual · $17.592.058",
                    "status": "active",
                    "next_on": "2026-11-26",
                    "created_at": "2026-10-30T14:00:00.000Z",
                    "updated_at": "2026-10-30T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/overdue-no-payment-seen": {
      "get": {
        "summary": "«Vencidas sin pago visto»: overdue payables kept out of the plan",
        "description": "Payables past their due date with no matching payment in the data Vitrina has (it does not claim they are unpaid: a card whose data stops earlier may have paid them), and no date given with «Mover pagos». They stay out of the projection and every headline. Each can be matched to a payment (Conciliación), marked paid (POST /finanzas/cash-flow/overdue-no-payment-seen/resolve) or given a date (POST /finanzas/cash-flow/moves with its `key`). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowOverdueNoPaymentSeenList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-11-09",
                    "count": 1,
                    "amount_clp": 405369,
                    "items": [
                      {
                        "key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                        "source": "payable",
                        "direction": "outflow",
                        "label": "Factura 21619609 · Empresa Nacional de Telecomunicaciones S.A.",
                        "amount_clp": 405369,
                        "date": "2026-08-31",
                        "due_date": "2026-08-31",
                        "overdue": true,
                        "movable": true,
                        "move": null,
                        "document": {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01",
                          "kind": "purchase",
                          "dte_type": 33,
                          "folio": "21619609"
                        },
                        "contraparte": null,
                        "recurring_item_id": null,
                        "rescue_id": null,
                        "count": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/cash-flow/overdue-no-payment-seen/resolve": {
      "post": {
        "summary": "Mark «Vencidas sin pago visto» as paid",
        "description": "The person knows these were paid (by card, cash, outside the banks Vitrina reads): each document leaves the list as paid. No bank link is invented. A document that is no longer an overdue open payable is skipped (`not_eligible`), never overwritten. One batch, one `batch_id`, audited as `finanzas.overdue_no_payment_seen.pagado`; POST /finanzas/inherited-overdue/batches/{batchId}/undo puts the batch back; POST /finanzas/documents/{id}/reopen reopens one. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read` and `finanzas:write` (the Contador seat reads, never writes).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FinanzasCashFlowOverdueResolveBody"
              },
              "example": {
                "document_ids": [
                  "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01"
                ],
                "note": "Pagada con la tarjeta Itaú"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Marked paid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCashFlowOverdueResolveResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "batch_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7e01",
                    "resolved": [
                      "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01"
                    ],
                    "skipped": [],
                    "amount_clp": 405369,
                    "remaining": {
                      "count": 0,
                      "amount_clp": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/alerts": {
      "get": {
        "summary": "Active Finanzas alerts",
        "description": "The alerts whose condition still holds: `cash_negative` (the Flujo de caja projection drops below zero in the next 30 days), `f29_due` (an F29 with something to pay is due within 5 days, unpaid) and `supplier_invoice_missing` (a Contraparte that invoiced each of the last 3 months has nothing this month, 3 days past its usual day). Evaluated hourly from 09:00 workspace time; each event is announced once to the owners and admins (bell and email by default; WhatsApp only on the opt-in `finanzas_whatsapp` rail) and leaves this list when its condition ends. Newest first. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise) and `finanzas:read`; the cash alert is listed only with `finanzas_reports:read` too.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The active alerts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAlertList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f8a01",
                      "kind": "f29_due",
                      "title": "El F29 de octubre vence el 20 de noviembre",
                      "body": "Hay que pagar $842.000 (monto estimado) al SII antes del 20 de noviembre. Cuando el pago aparezca en tus movimientos, el F29 queda como pagado.",
                      "cta_label": "Ver F29",
                      "path": "/finanzas/documentos?vista=f29",
                      "opened_on": "2026-11-15",
                      "notified_at": "2026-11-15T12:00:04.000Z",
                      "updated_at": "2026-11-16T12:00:03.000Z",
                      "data": {
                        "period": "2026-10",
                        "due_date": "2026-11-20",
                        "amount_clp": 842000,
                        "days_left": 4,
                        "amount_is_final": false,
                        "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7a01"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connect-setup": {
      "get": {
        "summary": "What is connected and what comes next (bank, SII, honorarios)",
        "description": "«Conecta tus cuentas»: the bank (Fintoc links, Mercado Pago, or bank movements already arriving by e-mail/cartola), the SII connection of one sociedad with its four steps (`razon_social`, `certificate`, `mandato`, `honorarios`, the Clave Tributaria for the boletas de honorarios) and `next_step`, where the guided flow resumes, plus the first-visit onboarding `mode` (`guided` | `checklist` | `done`). Step states: `todo`, `waiting` (the SII or the representante legal is on it), `done`, `problem` (with `reason`), `skipped` (only `honorarios`, after «Mi empresa no recibe boletas de honorarios», never while honorarios documents are on record). Reads the stored SII state; `refresh=1` reads the SII live. `connection_id` picks one sociedad (default: the first not yet complete). Connecting never turns on boleta emission. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1"
              ]
            },
            "required": false,
            "name": "refresh",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "connection_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The connect setup",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "bank": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done"
                              ]
                            },
                            "connected": {
                              "type": "integer"
                            },
                            "has_movements": {
                              "type": "boolean"
                            },
                            "direct": {
                              "type": "integer",
                              "description": "Live direct bank links (Fintoc / bank API); Mercado Pago is not counted."
                            },
                            "feed": {
                              "type": "string",
                              "enum": [
                                "direct",
                                "email",
                                "statement",
                                "none"
                              ],
                              "description": "`direct` = a live bank link reads the bank; `email` = movements arrive as bank notification e-mails (with some delay); `statement` = only imported statements (cartola, Chipax); `none` = nothing yet."
                            }
                          },
                          "required": [
                            "state",
                            "connected",
                            "has_movements",
                            "direct",
                            "feed"
                          ]
                        },
                        "mercado_pago": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done",
                                "problem"
                              ]
                            },
                            "relevant": {
                              "type": "boolean",
                              "description": "The workspace keeps a Mercado Pago account (e.g. from a Chipax import) or has a Mercado Pago connection: only then is the optional «Mercado Pago» row shown and counted. Never part of `onboarding.mode` `done`."
                            },
                            "evidence": {
                              "type": "boolean",
                              "description": "The workspace shows signs of using Mercado Pago: an account, a connection, or bank movements from any source naming it. With `state` `todo` and no `dismissed_at`, Resumen keeps a card with just this row."
                            },
                            "dismissed_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "When «Descartar» was pressed (`dismiss_mercado_pago`). Permanent per workspace; an unconnected, dismissed row is no longer `relevant`."
                            },
                            "nickname": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "connected_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "relevant",
                            "evidence",
                            "dismissed_at",
                            "nickname",
                            "connected_at"
                          ],
                          "description": "The optional Mercado Pago row. `done` = a live connection; `problem` = its pull is failing or late; `todo` = not connected (connect it with POST /payment-settings/mercadopago/connect)."
                        },
                        "sii": {
                          "type": "object",
                          "properties": {
                            "connection": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_entity_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "rut": {
                                  "type": "string"
                                },
                                "emission_on": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "id",
                                "legal_entity_id",
                                "legal_name",
                                "rut",
                                "emission_on"
                              ]
                            },
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "enum": [
                                      "razon_social",
                                      "certificate",
                                      "mandato",
                                      "honorarios"
                                    ]
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "todo",
                                      "waiting",
                                      "done",
                                      "problem",
                                      "skipped"
                                    ]
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "state",
                                  "reason"
                                ]
                              }
                            },
                            "mandato": {
                              "$ref": "#/components/schemas/FinanzasMandatoSignature"
                            },
                            "next_step": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "razon_social",
                                "certificate",
                                "mandato",
                                "honorarios"
                              ]
                            },
                            "complete": {
                              "type": "boolean"
                            },
                            "honorarios": {
                              "type": "object",
                              "properties": {
                                "evidence": {
                                  "type": "boolean"
                                },
                                "declared_none_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "can_skip": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "evidence",
                                "declared_none_at",
                                "can_skip"
                              ]
                            },
                            "sociedades": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "legal_name": {
                                    "type": "string"
                                  },
                                  "tax_id": {
                                    "type": "string"
                                  },
                                  "connected": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "id",
                                  "legal_name",
                                  "tax_id",
                                  "connected"
                                ]
                              }
                            },
                            "suggestion": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "source": {
                                  "type": "string",
                                  "enum": [
                                    "billing",
                                    "sii"
                                  ]
                                },
                                "rut": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "giro": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_street": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_number": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The house number, split off a billing street that carried it («Av. 21 de Mayo 1126» → street «Av. 21 de Mayo», number «1126»)."
                                },
                                "comuna_code": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The billing comuna, else the one the workspace's branch addresses name after the street; null when none resolves to exactly one."
                                },
                                "comuna_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "source",
                                "rut",
                                "legal_name",
                                "giro",
                                "address_street",
                                "address_number",
                                "comuna_code",
                                "comuna_name"
                              ]
                            },
                            "live": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "connection",
                            "steps",
                            "next_step",
                            "complete",
                            "honorarios",
                            "sociedades",
                            "suggestion",
                            "live"
                          ]
                        },
                        "onboarding": {
                          "type": "object",
                          "properties": {
                            "mode": {
                              "type": "string",
                              "enum": [
                                "guided",
                                "checklist",
                                "done"
                              ],
                              "description": "`guided` = the full-page start, only for a workspace with no Finanzas documents and no bank movements; `checklist` = the «Conecta tus cuentas» card on Resumen; `done` = bank and SII connected."
                            },
                            "started_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "deferred_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "mode",
                            "started_at",
                            "deferred_at"
                          ]
                        }
                      },
                      "required": [
                        "bank",
                        "mercado_pago",
                        "sii",
                        "onboarding"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "bank": {
                      "state": "done",
                      "connected": 1,
                      "has_movements": true
                    },
                    "sii": {
                      "connection": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "legal_entity_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                        "legal_name": "Clínica Ejemplo SpA",
                        "rut": "76123456-7",
                        "emission_on": false
                      },
                      "steps": [
                        {
                          "key": "razon_social",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "certificate",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "mandato",
                          "state": "waiting",
                          "reason": null
                        },
                        {
                          "key": "honorarios",
                          "state": "todo",
                          "reason": null
                        }
                      ],
                      "next_step": "honorarios",
                      "complete": false,
                      "honorarios": {
                        "evidence": false,
                        "declared_none_at": null,
                        "can_skip": true
                      },
                      "sociedades": [
                        {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                          "legal_name": "Clínica Ejemplo SpA",
                          "tax_id": "76123456-7",
                          "connected": true
                        }
                      ],
                      "suggestion": null,
                      "live": false
                    },
                    "onboarding": {
                      "mode": "guided",
                      "started_at": "2026-11-02T13:00:00.000Z",
                      "deferred_at": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connect-setup/sociedad-lookup": {
      "get": {
        "summary": "The SII's public record of a RUT (razón social prefill)",
        "description": "A suggestion for the razón social form: legal name and a giro that fits the SII's 40 characters, from the SII's public nómina of personas jurídicas. `null` when the RUT is not there (a persona natural) or the lookup is unavailable; 400 for an invalid RUT. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 3,
              "maxLength": 20
            },
            "required": true,
            "name": "rut",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The suggestion, or null",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "billing",
                            "sii"
                          ]
                        },
                        "rut": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "legal_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "giro": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "address_street": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "address_number": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The house number, split off a billing street that carried it («Av. 21 de Mayo 1126» → street «Av. 21 de Mayo», number «1126»)."
                        },
                        "comuna_code": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "The billing comuna, else the one the workspace's branch addresses name after the street; null when none resolves to exactly one."
                        },
                        "comuna_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "source",
                        "rut",
                        "legal_name",
                        "giro",
                        "address_street",
                        "address_number",
                        "comuna_code",
                        "comuna_name"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "source": "sii",
                    "rut": "76123456-7",
                    "legal_name": "CLINICA EJEMPLO SPA",
                    "giro": "SERVICIOS ODONTOLOGICOS",
                    "address_street": null,
                    "comuna_code": null,
                    "comuna_name": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connect-setup/sii": {
      "post": {
        "summary": "Connect a razón social to the SII (step 1)",
        "description": "Records the razón social when the workspace has none (`legal_entity`, the Datos legales body) or takes an existing one (`legal_entity_id`), then creates its SII connection. Idempotent per sociedad: an already connected one answers its connection. `resolucion` keeps an existing electronic-emission authorisation. Boleta emission stays OFF. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "legal_entity_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "legal_entity": {
                    "type": "object",
                    "properties": {
                      "legal_name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 300
                      },
                      "tax_id": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64
                      },
                      "tax_id_kind": {
                        "type": "string",
                        "enum": [
                          "rut",
                          "passport",
                          "foreign_tax_id"
                        ]
                      },
                      "giro": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 300
                      },
                      "address_street": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "address_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 40
                      },
                      "address_unit": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60
                      },
                      "comuna_code": {
                        "type": "string",
                        "pattern": "^[0-9]{5}$"
                      },
                      "representative_name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 300
                      },
                      "representative_tax_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "minLength": 1,
                        "maxLength": 64
                      },
                      "representative_tax_id_kind": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "rut",
                          "passport",
                          "foreign_tax_id"
                        ]
                      },
                      "escritura_date": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "escritura_notary": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "escritura_comuna_code": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^[0-9]{5}$"
                      }
                    },
                    "required": [
                      "legal_name",
                      "tax_id",
                      "giro",
                      "address_street",
                      "comuna_code",
                      "representative_name"
                    ]
                  },
                  "resolucion": {
                    "type": "object",
                    "properties": {
                      "numero": {
                        "type": "integer",
                        "minimum": 1
                      },
                      "fecha": {
                        "type": "string",
                        "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                      }
                    },
                    "required": [
                      "numero",
                      "fecha"
                    ]
                  }
                }
              },
              "example": {
                "legal_entity_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "connection_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "legal_entity_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "created_legal_entity": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "connection_id",
                        "legal_entity_id",
                        "created_legal_entity"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connection_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                    "legal_entity_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                    "created_legal_entity": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/connect-setup/sii/{id}/mandato": {
      "post": {
        "summary": "Vitrina's mandato for the representante legal to sign (step 3)",
        "description": "Renders Vitrina's mandato (`GET /legal/documents/{key}` with key `sii_mandato`, version + SHA-256) for this razón social and opens it in Vitrina's signing service. Returns the representante legal's `signing_url` (also mailed to them unless `send_email: false`). A new request replaces an unsigned one; a signed mandato cannot be replaced. Once signed it is registered at the SII automatically (`sii.mandato.phase`: `connecting` → `connected`). Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signer": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "rut": {
                        "type": "string",
                        "minLength": 3,
                        "maxLength": 20
                      },
                      "email": {
                        "type": "string",
                        "maxLength": 254,
                        "format": "email"
                      }
                    },
                    "description": "Who signs. Name and RUT default to the razón social's representante legal; the email is required (here or on file)."
                  },
                  "send_email": {
                    "type": "boolean",
                    "description": "Mail the signing link to the signer (default true)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "signer": {
                  "name": "María Pérez Soto",
                  "rut": "12.345.678-5",
                  "email": "maria@empresa.cl"
                },
                "send_email": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The signing request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "signing_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "status": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "signature": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/FinanzasMandatoSignature"
                            },
                            {
                              "type": "object"
                            }
                          ]
                        },
                        "email_sent": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "signing_url",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "signing_url": "https://sign.example.invalid/sign/abc",
                    "status": "pending",
                    "signature": {
                      "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b09",
                      "phase": "pending_signature",
                      "status": "pending_signature",
                      "terms_version": "vitrina-mandato-1.0",
                      "terms_sha256": "4398b7f80be1fd551c62325241e39c9e14138a63860f0cb4dcdc5be175f981b4",
                      "signer": {
                        "name": "María Pérez Soto",
                        "rut": "12.345.678-5",
                        "email": "maria@empresa.cl"
                      },
                      "signing_url": "https://sign.example.invalid/sign/abc",
                      "signed_at": null,
                      "attested_at": null,
                      "problem": null,
                      "document_available": false,
                      "created_at": "2026-11-02T15:00:00.000Z"
                    },
                    "email_sent": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/connect-setup/sii/{id}/mandato/document": {
      "get": {
        "summary": "The signed mandato PDF",
        "description": "The signed mandato of this razón social (`file=signed`, default) or the signing service's certificate (`file=certificate`). 404 until it is signed. Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "signed",
                "certificate"
              ]
            },
            "required": false,
            "name": "file",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/connect-setup/sii/{id}/mandato/revoke": {
      "post": {
        "summary": "Revoke the mandato (cláusula 7.1)",
        "description": "Revokes the signed mandato at the SII and here, with immediate effect: the SII connection stops acting for the empresa until a new mandato is signed. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The setup after the revocation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "bank": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done"
                              ]
                            },
                            "connected": {
                              "type": "integer"
                            },
                            "has_movements": {
                              "type": "boolean"
                            },
                            "direct": {
                              "type": "integer",
                              "description": "Live direct bank links (Fintoc / bank API); Mercado Pago is not counted."
                            },
                            "feed": {
                              "type": "string",
                              "enum": [
                                "direct",
                                "email",
                                "statement",
                                "none"
                              ],
                              "description": "`direct` = a live bank link reads the bank; `email` = movements arrive as bank notification e-mails (with some delay); `statement` = only imported statements (cartola, Chipax); `none` = nothing yet."
                            }
                          },
                          "required": [
                            "state",
                            "connected",
                            "has_movements",
                            "direct",
                            "feed"
                          ]
                        },
                        "mercado_pago": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done",
                                "problem"
                              ]
                            },
                            "relevant": {
                              "type": "boolean",
                              "description": "The workspace keeps a Mercado Pago account (e.g. from a Chipax import) or has a Mercado Pago connection: only then is the optional «Mercado Pago» row shown and counted. Never part of `onboarding.mode` `done`."
                            },
                            "evidence": {
                              "type": "boolean",
                              "description": "The workspace shows signs of using Mercado Pago: an account, a connection, or bank movements from any source naming it. With `state` `todo` and no `dismissed_at`, Resumen keeps a card with just this row."
                            },
                            "dismissed_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "When «Descartar» was pressed (`dismiss_mercado_pago`). Permanent per workspace; an unconnected, dismissed row is no longer `relevant`."
                            },
                            "nickname": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "connected_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "relevant",
                            "evidence",
                            "dismissed_at",
                            "nickname",
                            "connected_at"
                          ],
                          "description": "The optional Mercado Pago row. `done` = a live connection; `problem` = its pull is failing or late; `todo` = not connected (connect it with POST /payment-settings/mercadopago/connect)."
                        },
                        "sii": {
                          "type": "object",
                          "properties": {
                            "connection": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_entity_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "rut": {
                                  "type": "string"
                                },
                                "emission_on": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "id",
                                "legal_entity_id",
                                "legal_name",
                                "rut",
                                "emission_on"
                              ]
                            },
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "enum": [
                                      "razon_social",
                                      "certificate",
                                      "mandato",
                                      "honorarios"
                                    ]
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "todo",
                                      "waiting",
                                      "done",
                                      "problem",
                                      "skipped"
                                    ]
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "state",
                                  "reason"
                                ]
                              }
                            },
                            "mandato": {
                              "$ref": "#/components/schemas/FinanzasMandatoSignature"
                            },
                            "next_step": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "razon_social",
                                "certificate",
                                "mandato",
                                "honorarios"
                              ]
                            },
                            "complete": {
                              "type": "boolean"
                            },
                            "honorarios": {
                              "type": "object",
                              "properties": {
                                "evidence": {
                                  "type": "boolean"
                                },
                                "declared_none_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "can_skip": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "evidence",
                                "declared_none_at",
                                "can_skip"
                              ]
                            },
                            "sociedades": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "legal_name": {
                                    "type": "string"
                                  },
                                  "tax_id": {
                                    "type": "string"
                                  },
                                  "connected": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "id",
                                  "legal_name",
                                  "tax_id",
                                  "connected"
                                ]
                              }
                            },
                            "suggestion": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "source": {
                                  "type": "string",
                                  "enum": [
                                    "billing",
                                    "sii"
                                  ]
                                },
                                "rut": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "giro": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_street": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_number": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The house number, split off a billing street that carried it («Av. 21 de Mayo 1126» → street «Av. 21 de Mayo», number «1126»)."
                                },
                                "comuna_code": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The billing comuna, else the one the workspace's branch addresses name after the street; null when none resolves to exactly one."
                                },
                                "comuna_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "source",
                                "rut",
                                "legal_name",
                                "giro",
                                "address_street",
                                "address_number",
                                "comuna_code",
                                "comuna_name"
                              ]
                            },
                            "live": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "connection",
                            "steps",
                            "next_step",
                            "complete",
                            "honorarios",
                            "sociedades",
                            "suggestion",
                            "live"
                          ]
                        },
                        "onboarding": {
                          "type": "object",
                          "properties": {
                            "mode": {
                              "type": "string",
                              "enum": [
                                "guided",
                                "checklist",
                                "done"
                              ],
                              "description": "`guided` = the full-page start, only for a workspace with no Finanzas documents and no bank movements; `checklist` = the «Conecta tus cuentas» card on Resumen; `done` = bank and SII connected."
                            },
                            "started_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "deferred_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "mode",
                            "started_at",
                            "deferred_at"
                          ]
                        }
                      },
                      "required": [
                        "bank",
                        "mercado_pago",
                        "sii",
                        "onboarding"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "bank": {
                      "state": "done",
                      "connected": 1,
                      "has_movements": true
                    },
                    "sii": {
                      "connection": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "legal_entity_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                        "legal_name": "Clínica Ejemplo SpA",
                        "rut": "76123456-7",
                        "emission_on": false
                      },
                      "steps": [
                        {
                          "key": "razon_social",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "certificate",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "mandato",
                          "state": "waiting",
                          "reason": null
                        },
                        {
                          "key": "honorarios",
                          "state": "todo",
                          "reason": null
                        }
                      ],
                      "next_step": "honorarios",
                      "complete": false,
                      "honorarios": {
                        "evidence": false,
                        "declared_none_at": null,
                        "can_skip": true
                      },
                      "sociedades": [
                        {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                          "legal_name": "Clínica Ejemplo SpA",
                          "tax_id": "76123456-7",
                          "connected": true
                        }
                      ],
                      "suggestion": null,
                      "live": false
                    },
                    "onboarding": {
                      "mode": "guided",
                      "started_at": "2026-11-02T13:00:00.000Z",
                      "deferred_at": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "x-vitrina-connector-excluded": true
      }
    },
    "/finanzas/connect-setup/onboarding": {
      "post": {
        "summary": "Onboarding state: start, defer, or «no recibe boletas de honorarios»",
        "description": "Per workspace. `start` keeps the guided start while things get connected; `defer` («Lo hago después») collapses it to the checklist on Resumen; `no_honorarios` marks the honorarios step skipped (409 while honorarios documents are on record); `has_honorarios` undoes it; `dismiss_mercado_pago` hides the optional Mercado Pago card for good (per workspace, every device). Answers the setup. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise). Requires `finanzas_admin:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "start",
                      "defer",
                      "no_honorarios",
                      "has_honorarios",
                      "dismiss_mercado_pago"
                    ]
                  }
                },
                "required": [
                  "action"
                ]
              },
              "example": {
                "action": "defer"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The setup",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "bank": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done"
                              ]
                            },
                            "connected": {
                              "type": "integer"
                            },
                            "has_movements": {
                              "type": "boolean"
                            },
                            "direct": {
                              "type": "integer",
                              "description": "Live direct bank links (Fintoc / bank API); Mercado Pago is not counted."
                            },
                            "feed": {
                              "type": "string",
                              "enum": [
                                "direct",
                                "email",
                                "statement",
                                "none"
                              ],
                              "description": "`direct` = a live bank link reads the bank; `email` = movements arrive as bank notification e-mails (with some delay); `statement` = only imported statements (cartola, Chipax); `none` = nothing yet."
                            }
                          },
                          "required": [
                            "state",
                            "connected",
                            "has_movements",
                            "direct",
                            "feed"
                          ]
                        },
                        "mercado_pago": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "todo",
                                "done",
                                "problem"
                              ]
                            },
                            "relevant": {
                              "type": "boolean",
                              "description": "The workspace keeps a Mercado Pago account (e.g. from a Chipax import) or has a Mercado Pago connection: only then is the optional «Mercado Pago» row shown and counted. Never part of `onboarding.mode` `done`."
                            },
                            "evidence": {
                              "type": "boolean",
                              "description": "The workspace shows signs of using Mercado Pago: an account, a connection, or bank movements from any source naming it. With `state` `todo` and no `dismissed_at`, Resumen keeps a card with just this row."
                            },
                            "dismissed_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "When «Descartar» was pressed (`dismiss_mercado_pago`). Permanent per workspace; an unconnected, dismissed row is no longer `relevant`."
                            },
                            "nickname": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "connected_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "relevant",
                            "evidence",
                            "dismissed_at",
                            "nickname",
                            "connected_at"
                          ],
                          "description": "The optional Mercado Pago row. `done` = a live connection; `problem` = its pull is failing or late; `todo` = not connected (connect it with POST /payment-settings/mercadopago/connect)."
                        },
                        "sii": {
                          "type": "object",
                          "properties": {
                            "connection": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_entity_id": {
                                  "type": "string",
                                  "format": "uuid"
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "rut": {
                                  "type": "string"
                                },
                                "emission_on": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "id",
                                "legal_entity_id",
                                "legal_name",
                                "rut",
                                "emission_on"
                              ]
                            },
                            "steps": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "enum": [
                                      "razon_social",
                                      "certificate",
                                      "mandato",
                                      "honorarios"
                                    ]
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "todo",
                                      "waiting",
                                      "done",
                                      "problem",
                                      "skipped"
                                    ]
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "state",
                                  "reason"
                                ]
                              }
                            },
                            "mandato": {
                              "$ref": "#/components/schemas/FinanzasMandatoSignature"
                            },
                            "next_step": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "razon_social",
                                "certificate",
                                "mandato",
                                "honorarios"
                              ]
                            },
                            "complete": {
                              "type": "boolean"
                            },
                            "honorarios": {
                              "type": "object",
                              "properties": {
                                "evidence": {
                                  "type": "boolean"
                                },
                                "declared_none_at": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "can_skip": {
                                  "type": "boolean"
                                }
                              },
                              "required": [
                                "evidence",
                                "declared_none_at",
                                "can_skip"
                              ]
                            },
                            "sociedades": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "legal_name": {
                                    "type": "string"
                                  },
                                  "tax_id": {
                                    "type": "string"
                                  },
                                  "connected": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "id",
                                  "legal_name",
                                  "tax_id",
                                  "connected"
                                ]
                              }
                            },
                            "suggestion": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "source": {
                                  "type": "string",
                                  "enum": [
                                    "billing",
                                    "sii"
                                  ]
                                },
                                "rut": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "legal_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "giro": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_street": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address_number": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The house number, split off a billing street that carried it («Av. 21 de Mayo 1126» → street «Av. 21 de Mayo», number «1126»)."
                                },
                                "comuna_code": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The billing comuna, else the one the workspace's branch addresses name after the street; null when none resolves to exactly one."
                                },
                                "comuna_name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "source",
                                "rut",
                                "legal_name",
                                "giro",
                                "address_street",
                                "address_number",
                                "comuna_code",
                                "comuna_name"
                              ]
                            },
                            "live": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "connection",
                            "steps",
                            "next_step",
                            "complete",
                            "honorarios",
                            "sociedades",
                            "suggestion",
                            "live"
                          ]
                        },
                        "onboarding": {
                          "type": "object",
                          "properties": {
                            "mode": {
                              "type": "string",
                              "enum": [
                                "guided",
                                "checklist",
                                "done"
                              ],
                              "description": "`guided` = the full-page start, only for a workspace with no Finanzas documents and no bank movements; `checklist` = the «Conecta tus cuentas» card on Resumen; `done` = bank and SII connected."
                            },
                            "started_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "deferred_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "mode",
                            "started_at",
                            "deferred_at"
                          ]
                        }
                      },
                      "required": [
                        "bank",
                        "mercado_pago",
                        "sii",
                        "onboarding"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "bank": {
                      "state": "done",
                      "connected": 1,
                      "has_movements": true
                    },
                    "sii": {
                      "connection": {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b01",
                        "legal_entity_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                        "legal_name": "Clínica Ejemplo SpA",
                        "rut": "76123456-7",
                        "emission_on": false
                      },
                      "steps": [
                        {
                          "key": "razon_social",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "certificate",
                          "state": "done",
                          "reason": null
                        },
                        {
                          "key": "mandato",
                          "state": "waiting",
                          "reason": null
                        },
                        {
                          "key": "honorarios",
                          "state": "todo",
                          "reason": null
                        }
                      ],
                      "next_step": "honorarios",
                      "complete": false,
                      "honorarios": {
                        "evidence": false,
                        "declared_none_at": null,
                        "can_skip": true
                      },
                      "sociedades": [
                        {
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f5b02",
                          "legal_name": "Clínica Ejemplo SpA",
                          "tax_id": "76123456-7",
                          "connected": true
                        }
                      ],
                      "suggestion": null,
                      "live": false
                    },
                    "onboarding": {
                      "mode": "guided",
                      "started_at": "2026-11-02T13:00:00.000Z",
                      "deferred_at": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/finanzas/digest/preview": {
      "get": {
        "summary": "Preview the weekly Finanzas digest",
        "description": "What this week's «Resumen semanal de Finanzas» says to the caller, read now from the Resumen (`GET /finanzas/resumen`), so the figures match the home. The digest goes out every Monday from 09:00 workspace time, at most once per workspace and week, to every member with `finanzas:read`: email by default, WhatsApp only on the opt-in `finanzas_whatsapp` rail; each member turns it off in their notification preferences (`finanzas.weekly_digest`). A workspace with no finance data gets none (`will_send: false`). Without `finanzas_reports:read` there are no cash or result figures; without the patient roster patients read as initials. Sends nothing. Requires the `finanzas` add-on (402 `ENTITLEMENT_NOT_ACTIVE` otherwise) and `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The digest as the caller would read it this week",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasDigestPreview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "week_start": "2026-11-02",
                    "send_at": "2026-11-02T12:00:00.000Z",
                    "next_send_at": "2026-11-09T12:00:00.000Z",
                    "status": "pending",
                    "sent_at": null,
                    "will_send": true,
                    "channels": {
                      "email": true,
                      "whatsapp": false
                    },
                    "email": {
                      "subject": "Resumen semanal de Finanzas: $12.340.000 en caja",
                      "hero": {
                        "label": "Caja hoy",
                        "value": "$12.340.000",
                        "caption": "A fin de mes: $8.200.000. En 30 días: $9.100.000."
                      },
                      "problem": {
                        "text": "La caja alcanza hasta el 23 de noviembre. Ese martes pagas Arriendo y quedarías en negativo por $450.000.",
                        "link": {
                          "label": "Ver flujo de caja y cómo cubrirlo",
                          "path": "/finanzas/reportes/flujo-de-caja"
                        }
                      },
                      "lines": [
                        "Noviembre va en $6.100.000 de ingresos, de $9.800.000 para cubrir costos.",
                        "3 casos te necesitan en Por resolver, por $840.000."
                      ],
                      "primary": {
                        "label": "Ver Resumen",
                        "path": "/finanzas"
                      },
                      "secondary": {
                        "label": "Ir a Por resolver",
                        "path": "/finanzas/por-resolver"
                      }
                    },
                    "whatsapp_text": "Caja hoy: $12.340.000. La caja alcanza hasta el 23 de noviembre. Ese martes pagas Arriendo y quedarías en negativo por $450.000. 3 casos te necesitan en Por resolver, por $840.000.",
                    "figures": {
                      "reports_visible": true,
                      "cash_today_clp": 12340000,
                      "month_end_clp": 8200000,
                      "in_30_days_clp": 9100000,
                      "runs_out_date": "2026-11-24",
                      "ingresos_clp": 6100000,
                      "resultado_clp": -1200000,
                      "equilibrio_clp": 9800000,
                      "open_cases": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/costos": {
      "get": {
        "summary": "Costos y equilibrio: fixed vs variable costs and the break-even",
        "description": "Reportes › Costos y equilibrio, from the classified result (the same net amounts as the Resultado). Per month and for the period: ingresos, costos variables (accounts flagged variable, documented or not, plus «Egresos sin clasificar», reported apart in `sin_clasificar_clp`), costos fijos, margen de contribución and its %, and the **punto de equilibrio** = fijos ÷ margen de contribución %, the monthly income at which the result is zero, with income against it (`brecha_clp`, `cobertura_pct`). An account without its own flag takes its nearest ancestor’s. Clinics also get `dentist_share` (the treating dentist’s share, a variable cost, with the optional reference line, «Un tercio de la venta» by default) and `hora_sillon`: fijos ÷ available chair-hours (chairs × opening hours of each branch; the current month counts the days lived so far), all costs ÷ attended hours (completed citas), occupancy and production per attended hour, per branch and total. Other verticals see break-even only. Default: January of the current year to the current month; at most 24 months. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of `to`’s year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: the current month). At most 24 months."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCostos"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-09",
                    "to": "2026-10",
                    "current_month": "2026-10",
                    "currency": "CLP",
                    "is_clinic": true,
                    "reference_month": "2026-09",
                    "months": [
                      {
                        "month": "2026-09",
                        "label": "septiembre 2026",
                        "proforma": false,
                        "ingresos_clp": 48000000,
                        "variables_clp": 24000000,
                        "sin_clasificar_clp": 0,
                        "fijos_clp": 26000000,
                        "contribucion_clp": 24000000,
                        "contribucion_pct": 50,
                        "equilibrio_clp": 52000000,
                        "equilibrio_sin_calculo": null,
                        "brecha_clp": -4000000,
                        "cobertura_pct": 92.3,
                        "resultado_clp": -2000000
                      },
                      {
                        "month": "2026-10",
                        "label": "octubre 2026",
                        "proforma": true,
                        "ingresos_clp": 30000000,
                        "variables_clp": 14000000,
                        "sin_clasificar_clp": 0,
                        "fijos_clp": 25000000,
                        "contribucion_clp": 16000000,
                        "contribucion_pct": 53.3,
                        "equilibrio_clp": 46875000,
                        "equilibrio_sin_calculo": null,
                        "brecha_clp": -16875000,
                        "cobertura_pct": 64,
                        "resultado_clp": -9000000
                      }
                    ],
                    "period": {
                      "ingresos_clp": 39000000,
                      "variables_clp": 19000000,
                      "sin_clasificar_clp": 0,
                      "fijos_clp": 25500000,
                      "contribucion_clp": 20000000,
                      "contribucion_pct": 51.3,
                      "equilibrio_clp": 49725000,
                      "equilibrio_sin_calculo": null,
                      "brecha_clp": -10725000,
                      "cobertura_pct": 78.4,
                      "resultado_clp": -5500000,
                      "months": 2
                    },
                    "accounts": [
                      {
                        "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6101",
                        "name": "Dentistas (honorarios y liquidaciones)",
                        "path": "Costos de explotación › Dentistas (honorarios y liquidaciones)",
                        "kind": "costo",
                        "behavior": "variable",
                        "behavior_inherited": false,
                        "values": [
                          16000000,
                          9500000
                        ],
                        "total_clp": 25500000
                      },
                      {
                        "account_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6102",
                        "name": "Arriendo",
                        "path": "Gastos de administración y venta › Gastos generales › Arriendo",
                        "kind": "gasto",
                        "behavior": "fijo",
                        "behavior_inherited": false,
                        "values": [
                          4200000,
                          4200000
                        ],
                        "total_clp": 8400000
                      }
                    ],
                    "dentist_share": {
                      "account_ids": [
                        "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6101"
                      ],
                      "source": "detected",
                      "values": [
                        16000000,
                        9500000
                      ],
                      "total_clp": 25500000,
                      "pct": [
                        33.3,
                        31.7
                      ],
                      "pct_total": 32.7,
                      "reference": {
                        "enabled": true,
                        "share_bps": 3333,
                        "label": "Un tercio de la venta",
                        "values": [
                          15998400,
                          9999000
                        ],
                        "total_clp": 25997400
                      }
                    },
                    "hora_sillon": {
                      "chairs": 6,
                      "chairs_source": {
                        "count": 6,
                        "source": "resources",
                        "source_label": "de tu agenda"
                      },
                      "lunch_break_minutes": 60,
                      "available_hours": [
                        1440,
                        1056
                      ],
                      "attended_hours": [
                        1087,
                        780.5
                      ],
                      "available_hours_total": 2496,
                      "attended_hours_total": 1867.5,
                      "production_clp": [
                        52000000,
                        33000000
                      ],
                      "production_total_clp": 85000000,
                      "production_source": "clinic_mirror",
                      "occupancy_pct": [
                        75.5,
                        73.9
                      ],
                      "occupancy_pct_total": 74.8,
                      "costo_hora_disponible_clp": [
                        18056,
                        23674
                      ],
                      "costo_hora_disponible_total_clp": 20433,
                      "costo_hora_atendida_clp": [
                        45998,
                        50000
                      ],
                      "costo_hora_atendida_total_clp": 47657,
                      "produccion_hora_atendida_clp": [
                        47838,
                        42281
                      ],
                      "produccion_hora_atendida_total_clp": 45515,
                      "branches": [
                        {
                          "branch_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6201",
                          "name": "Casa matriz",
                          "chairs": 6,
                          "available_hours": [
                            1440,
                            1056
                          ],
                          "attended_hours": [
                            1087,
                            780.5
                          ],
                          "available_hours_total": 2496,
                          "attended_hours_total": 1867.5,
                          "occupancy_pct": 74.8
                        }
                      ],
                      "missing": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/costos/settings": {
      "get": {
        "summary": "Read how Costos y equilibrio reads the dentist share",
        "description": "Which accounts are the treating dentist’s share (null = detected: the clinic chart’s «Dentistas» account, or accounts named «Dentista…», «Profesionales…» or «Honorarios clínicos…»), and the optional reference line (on by default at 3333 bps, a third of the sale). Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCostosSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reference_share_enabled": true,
                    "reference_share_bps": 3333,
                    "professional_account_ids": null,
                    "effective_professional_account_ids": [
                      "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6101"
                    ],
                    "chairs_override": null,
                    "lunch_break_minutes": null,
                    "updated_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6301",
                    "updated_at": "2026-10-31T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Change the dentist share accounts or the reference line",
        "description": "Send only what changes. `professional_account_ids`: costo or gasto accounts of the plan de cuentas (null goes back to detection); `reference_share_enabled` toggles the line; `reference_share_bps` is its share of income (1–10000). `chairs_override` (1–200) sets the clinic’s chair count, shown as «elegido por ti»; null goes back to the clinic’s resources. `lunch_break_minutes` (0–240; null = 60) is the weekday break taken off the available hours. 400 `VALIDATION_ERROR` with `details.reason` `account_not_found` or `account_not_cost`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reference_share_enabled": {
                    "type": "boolean"
                  },
                  "reference_share_bps": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10000
                  },
                  "professional_account_ids": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50
                  },
                  "chairs_override": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 200,
                    "description": "Set the number of chairs, or null to go back to the clinic’s resources."
                  },
                  "lunch_break_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 240,
                    "description": "Weekday lunch break in minutes; null = default (60)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reference_share_enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasCostosSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reference_share_enabled": true,
                    "reference_share_bps": 3333,
                    "professional_account_ids": null,
                    "effective_professional_account_ids": [
                      "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6101"
                    ],
                    "chairs_override": null,
                    "lunch_break_minutes": null,
                    "updated_by": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6301",
                    "updated_at": "2026-10-31T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/resumen": {
      "get": {
        "summary": "Resumen: the Finanzas home, three answers and the context",
        "description": "The Finanzas home in one read, every figure from its detail screen: **cash** («¿cuánto me queda a fin de mes?») = the Flujo de caja projection — today’s balance of the operating accounts, the month-end and 30-day balance, the lowest point and `runs_out` (the first day under zero within 30 days, with its biggest payment); **result** («¿cuánto gasto? / ¿este mes gana plata?») = Costos y equilibrio for the current month at net amounts (ingresos, gastos = variables + fijos, «sin clasificar» apart and counted as variable, resultado operacional) against the punto de equilibrio of the last closed month; **inbox** («Te necesita») = the open «Por resolver» cases and the previous month’s close state; **automatic** = this month’s «Hecho por Vitrina» totals and the newest 3; **accounts** with their balances. `problem` is the one thing shown in red (cash running out, else a bank connection that needs a person). A caller without `finanzas_reports:read` gets `cash`, `result` and every account balance (`balance_clp`, `balance_as_of`) null; account names stay (`reports_visible: false`). A Contador sees patients as initials. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The Resumen",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasResumen"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-10-30",
                    "month": "2026-10",
                    "month_label": "octubre 2026",
                    "currency": "CLP",
                    "reports_visible": true,
                    "synced_at": "2026-10-30T11:02:00.000Z",
                    "problem": {
                      "kind": "cash_runs_out",
                      "date": "2026-11-06",
                      "balance_clp": -412000
                    },
                    "cash": {
                      "today_clp": 4917109,
                      "balance_as_of": "2026-10-30T11:02:00.000Z",
                      "accounts_count": 3,
                      "unknown_count": 0,
                      "savings_clp": 12000000,
                      "month_end": {
                        "date": "2026-10-31",
                        "balance_clp": 5102000,
                        "band": {
                          "low_clp": 4980000,
                          "high_clp": 5260000
                        }
                      },
                      "in_30_days": {
                        "date": "2026-11-29",
                        "balance_clp": 3880000,
                        "band": {
                          "low_clp": 2100000,
                          "high_clp": 5400000
                        }
                      },
                      "lowest": {
                        "date": "2026-11-06",
                        "balance_clp": -412000,
                        "negative": true,
                        "is_today": false,
                        "cause": {
                          "key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6501",
                          "source": "payable",
                          "direction": "outflow",
                          "label": "Factura 4410 · Laboratorio Andes SpA",
                          "amount_clp": 5800000,
                          "date": "2026-11-06",
                          "due_date": "2026-11-06",
                          "overdue": false,
                          "movable": true,
                          "move": null,
                          "document": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6501",
                            "kind": "purchase",
                            "dte_type": 33,
                            "folio": "4410"
                          },
                          "contraparte": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6601",
                            "name": "Laboratorio Andes SpA"
                          },
                          "recurring_item_id": null,
                          "rescue_id": null,
                          "count": null
                        }
                      },
                      "runs_out": {
                        "date": "2026-11-06",
                        "covered_until": "2026-11-05",
                        "balance_clp": -412000,
                        "cause": {
                          "key": "document:0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6501",
                          "source": "payable",
                          "direction": "outflow",
                          "label": "Factura 4410 · Laboratorio Andes SpA",
                          "amount_clp": 5800000,
                          "date": "2026-11-06",
                          "due_date": "2026-11-06",
                          "overdue": false,
                          "movable": true,
                          "move": null,
                          "document": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6501",
                            "kind": "purchase",
                            "dte_type": 33,
                            "folio": "4410"
                          },
                          "contraparte": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6601",
                            "name": "Laboratorio Andes SpA"
                          },
                          "recurring_item_id": null,
                          "rescue_id": null,
                          "count": null
                        }
                      },
                      "overdue": {
                        "payables_clp": 0,
                        "payables_count": 0,
                        "stale_payables_clp": 0,
                        "stale_payables_count": 0,
                        "receivables_clp": 380000,
                        "receivables_count": 2
                      },
                      "inherited_overdue": {
                        "count": 0,
                        "amount_clp": 0
                      },
                      "points": [
                        {
                          "date": "2026-10-30",
                          "balance_clp": 4917109,
                          "band": {
                            "low_clp": 4917109,
                            "high_clp": 4917109
                          }
                        },
                        {
                          "date": "2026-10-31",
                          "balance_clp": 5102000,
                          "band": {
                            "low_clp": 4980000,
                            "high_clp": 5260000
                          }
                        }
                      ]
                    },
                    "result": {
                      "month": "2026-10",
                      "label": "octubre 2026",
                      "proforma": true,
                      "ingresos_clp": 41200000,
                      "gastos_clp": 38900000,
                      "variables_clp": 17400000,
                      "fijos_clp": 21500000,
                      "sin_clasificar_clp": 900000,
                      "resultado_clp": 2300000,
                      "equilibrio": {
                        "month": "2026-09",
                        "label": "septiembre 2026",
                        "equilibrio_clp": 44600000,
                        "sin_calculo": null,
                        "produccion": null,
                        "skipped_incompleto": null
                      },
                      "target_clp": 44600000,
                      "cobertura_pct": 92.4,
                      "brecha_clp": -3400000
                    },
                    "inbox": {
                      "open": 4,
                      "amount_at_stake_clp": 2860000,
                      "by_kind": {
                        "reconcile": 2,
                        "transfer": 0,
                        "classify": 1,
                        "missing_document": 1,
                        "unknown": 0
                      },
                      "close": {
                        "month": "2026-09",
                        "label": "septiembre 2026",
                        "state": "closed",
                        "open_cases": 0
                      }
                    },
                    "automatic": {
                      "month": "2026-10",
                      "count": 240,
                      "total_clp": 61200000,
                      "unreviewed_count": 31,
                      "by_actor": {
                        "rule": 150,
                        "match": 72,
                        "ai": 18
                      },
                      "recent": [
                        {
                          "kind": "link",
                          "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6701",
                          "actor": "match",
                          "label": "Pago de la factura 4388 de Laboratorio Andes SpA",
                          "day": "2026-10-29",
                          "amount_clp": 1240000,
                          "applied_at": "2026-10-29T14:10:00.000Z",
                          "movement": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6801",
                            "day": "2026-10-29",
                            "amount_clp": 1240000,
                            "direction": "outbound",
                            "description": "Transferencia a Laboratorio Andes"
                          },
                          "document": null,
                          "target": "document",
                          "reasons": [
                            "rut",
                            "amount"
                          ],
                          "account": null,
                          "split": false,
                          "rule": null,
                          "reviewed_at": null,
                          "reviewed_by": null,
                          "group_name": "Laboratorio Andes SpA",
                          "group_key": "contraparte:3f6c1d2e-5b7a-4c1e-9d0f-2a8b6c4e1f70",
                          "group_count": 1,
                          "group_total_clp": 1240000
                        }
                      ]
                    },
                    "accounts": [
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6401",
                        "name": "Cuenta corriente",
                        "institution_name": "Banco de Chile",
                        "number_masked": "··4821",
                        "type": "cuenta_corriente",
                        "currency": "CLP",
                        "balance_clp": 3719083,
                        "balance_as_of": "2026-10-30T11:02:00.000Z",
                        "in_cash": true
                      },
                      {
                        "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f6402",
                        "name": "Ahorro",
                        "institution_name": "Banco de Chile",
                        "number_masked": "··0057",
                        "type": "cuenta_ahorro",
                        "currency": "CLP",
                        "balance_clp": 12000000,
                        "balance_as_of": "2026-10-30T11:02:00.000Z",
                        "in_cash": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/produccion": {
      "get": {
        "summary": "Producción, cobrado y boleteado (clinics)",
        "description": "The clinic’s bridge per month and for the range: **producción** (treatments performed), **cobrado** (patient payments received) and **boleteado** (boletas DTE 39/41 issued, at totals, minus notas de crédito on boletas), with **por cobrar** (produced, not paid) and **anticipos** (paid, not produced) per patient, so that producción − por cobrar + anticipos = cobrado; the range column nets per patient. Clinic money authority: a clinic mirrored from Dentalink/Medilink reads producción and cobrado only from the mirror (`source: clinic_mirror`), otherwise from Vitrina’s registers; bank movements are never income. Boletas are checked against payments both ways (`payments_without_boleta`, `boletas_without_payment`): first the link the clinic system holds, then a boleta to the payment’s own patient, then a monthly summary boleta (to the generic receiver 66.666.666-6 or to nobody, named by no payment) covers its month’s remaining payments (`monthly_boletas`); a month’s leftover is then covered by what the NEXT month’s summary has left (month-end cash is billed in the next summary), shown as the signed `boleteado_otro_mes_clp`, and a leftover whose next summary is not issued yet is `resumen_pendiente`, not a gap. `payments_without_boleta` is also grouped `by_month` (`per_month` items each); `sin_boleta_month` lists one month completely. `ingresos_resultado_clp` is the Resultado’s income for the same months, to read next to it. A caller without the patient roster sees «Paciente A. P.» and no RUT. Other verticals answer `is_clinic: false` with empty figures. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_reports:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "First month (default: January of `to`’s year)."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Last month (default: the current month). At most 24 months."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Most mismatch items listed per side (1–200, default 50, largest first); `count` and `total_clp` always cover them all."
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "description": "Items each `payments_without_boleta.by_month` group carries (1–50, default 3, largest first)."
            },
            "required": false,
            "name": "per_month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Scope `payments_without_boleta` (`items`, `count`, `total_clp`, `by_month`) to this month, which must be inside `from`..`to` (else 400 `VALIDATION_ERROR`). Use it with `limit` (up to 200) to list one month completely; the other sections are unchanged."
            },
            "required": false,
            "name": "sin_boleta_month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The bridge",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasPuente"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-10",
                    "to": "2026-10",
                    "current_month": "2026-11",
                    "currency": "CLP",
                    "is_clinic": true,
                    "source": "clinic_mirror",
                    "boletas_source": "finanzas",
                    "months": [
                      {
                        "month": "2026-10",
                        "label": "octubre 2026",
                        "proforma": false,
                        "produccion_clp": 61340000,
                        "cobrado_clp": 58204222,
                        "boleteado_clp": 57726146,
                        "por_cobrar_clp": 4955778,
                        "anticipos_clp": 1820000,
                        "cobrado_sin_boleta_clp": 478076,
                        "boletas_sin_pago_clp": 0,
                        "ingresos_resultado_clp": 57726146,
                        "pagos_sin_boleta": 3,
                        "pendiente_resumen_clp": 0,
                        "pagos_pendiente_resumen": 0,
                        "boleteado_otro_mes_clp": 0
                      }
                    ],
                    "total": {
                      "produccion_clp": 61340000,
                      "cobrado_clp": 58204222,
                      "boleteado_clp": 57726146,
                      "por_cobrar_clp": 4955778,
                      "anticipos_clp": 1820000,
                      "cobrado_sin_boleta_clp": 478076,
                      "boletas_sin_pago_clp": 0,
                      "ingresos_resultado_clp": 57726146,
                      "pagos_sin_boleta": 3,
                      "pendiente_resumen_clp": 0,
                      "pagos_pendiente_resumen": 0,
                      "boleteado_otro_mes_clp": 0
                    },
                    "payments_without_boleta": {
                      "count": 3,
                      "total_clp": 478076,
                      "items": [
                        {
                          "payment_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7101",
                          "source": "clinic_mirror",
                          "day": "2026-10-31",
                          "amount_clp": 250000,
                          "without_boleta_clp": 250000,
                          "reason": "sin_boleta",
                          "folios": [],
                          "patient": {
                            "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7301",
                            "name": "Paciente A. P.",
                            "rut": null
                          }
                        }
                      ],
                      "by_month": [
                        {
                          "month": "2026-10",
                          "count": 3,
                          "total_clp": 478076,
                          "items": [
                            {
                              "payment_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7101",
                              "source": "clinic_mirror",
                              "day": "2026-10-31",
                              "amount_clp": 250000,
                              "without_boleta_clp": 250000,
                              "reason": "sin_boleta",
                              "folios": [],
                              "patient": {
                                "id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7301",
                                "name": "Paciente A. P.",
                                "rut": null
                              }
                            }
                          ]
                        }
                      ]
                    },
                    "boletas_without_payment": {
                      "count": 0,
                      "total_clp": 0,
                      "items": []
                    },
                    "monthly_boletas": [
                      {
                        "document_id": "0190a3c2-5d1e-7b6a-9f20-4c1d2e3f7201",
                        "dte_type": 41,
                        "folio": "1204",
                        "day": "2026-10-31",
                        "month": "2026-10",
                        "total_clp": 57726146,
                        "covered_clp": 57726146,
                        "payments": 412
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contador-export": {
      "get": {
        "summary": "Download the Contador package for a month (zip)",
        "description": "One zip, `paquete-contador-<period>.zip`, with seven xlsx: `01-libro-de-compras`, `02-libro-de-ventas`, `03-honorarios`, `04-movimientos-por-cuenta` (one sheet per bank account), `05-clasificaciones-por-cuenta` (net, as the Resultado counts), `06-resultado` and `07-ajustes-posteriores-al-cierre`, each `<name>-<period>.xlsx`, built by the same code as each screen and its export. Read-only. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» with no RUT; the libro de ventas keeps the patient RUT on facturas and on notas de crédito or débito that cite one, and boletas carry neither RUT nor name. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`, `finanzas_reports:read` and `finanzas:export` (the Contador seat, owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM` (default: the month before the current one, America/Santiago). Up to the current month; a future month answers 400."
            },
            "required": false,
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The package, as an attachment",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/contador-export/manifest": {
      "get": {
        "summary": "What the Contador package for a month holds",
        "description": "The same package as the download, without the bytes: the period, the zip name and each file (key, label, name, rows, size). Over MCP this is the read that generates and checks the package; the zip itself downloads over HTTPS. A caller without the patient register (the Contador) sees a patient as «Paciente A. P.» with no RUT; the libro de ventas keeps the patient RUT on facturas and on notas de crédito or débito that cite one, and boletas carry neither RUT nor name. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`, `finanzas_reports:read` and `finanzas:export` (the Contador seat, owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "The month, `YYYY-MM` (default: the month before the current one, America/Santiago). Up to the current month; a future month answers 400."
            },
            "required": false,
            "name": "period",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "string",
                          "description": "The month, `YYYY-MM`."
                        },
                        "filename": {
                          "type": "string",
                          "description": "The zip the download serves."
                        },
                        "files": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "libro_compras",
                                  "libro_ventas",
                                  "honorarios",
                                  "movimientos",
                                  "clasificaciones",
                                  "resultado",
                                  "ajustes"
                                ]
                              },
                              "label": {
                                "type": "string"
                              },
                              "filename": {
                                "type": "string",
                                "description": "The xlsx inside the zip."
                              },
                              "rows": {
                                "type": "integer",
                                "description": "Rows of its «Detalle» sheet (documents, items) when it has one, else of its first sheet (movements, adjustments)."
                              },
                              "size_bytes": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "key",
                              "label",
                              "filename",
                              "rows",
                              "size_bytes"
                            ]
                          }
                        }
                      },
                      "required": [
                        "period",
                        "filename",
                        "files"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": "2026-09",
                    "filename": "paquete-contador-2026-09.zip",
                    "files": [
                      {
                        "key": "libro_compras",
                        "label": "Libro de compras",
                        "filename": "01-libro-de-compras-2026-09.xlsx",
                        "rows": 84,
                        "size_bytes": 14210
                      },
                      {
                        "key": "libro_ventas",
                        "label": "Libro de ventas",
                        "filename": "02-libro-de-ventas-2026-09.xlsx",
                        "rows": 1312,
                        "size_bytes": 61904
                      },
                      {
                        "key": "honorarios",
                        "label": "Honorarios",
                        "filename": "03-honorarios-2026-09.xlsx",
                        "rows": 12,
                        "size_bytes": 7480
                      },
                      {
                        "key": "movimientos",
                        "label": "Movimientos por cuenta",
                        "filename": "04-movimientos-por-cuenta-2026-09.xlsx",
                        "rows": 640,
                        "size_bytes": 38112
                      },
                      {
                        "key": "clasificaciones",
                        "label": "Clasificaciones por cuenta",
                        "filename": "05-clasificaciones-por-cuenta-2026-09.xlsx",
                        "rows": 702,
                        "size_bytes": 40356
                      },
                      {
                        "key": "resultado",
                        "label": "Resultado",
                        "filename": "06-resultado-2026-09.xlsx",
                        "rows": 1398,
                        "size_bytes": 52018
                      },
                      {
                        "key": "ajustes",
                        "label": "Ajustes posteriores al cierre",
                        "filename": "07-ajustes-posteriores-al-cierre-2026-09.xlsx",
                        "rows": 3,
                        "size_bytes": 6920
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/ads-connections": {
      "get": {
        "summary": "Ad accounts reachable for Cargos de publicidad",
        "description": "Per platform (Meta Ads, Google Ads): the ad accounts connected to the workspace, their status, last sync and the charges synced and linked, from the last read of the ad data. Each platform says what happens without a connection; connecting is Integraciones › Meta Ads. A card movement whose descriptor names Google Ads (`GOOGLE *ADS<customer id>`) or Meta (`FACEBK *<ref>`, `META*…`, `META PLATFORMS…`) becomes a `manual_expense` document of Contraparte «Google Ads» / «Meta Platforms» (`GET /finanzas/documents?origin=ads`); a refund (money in) a nota de crédito. Vitrina links it when the ad data makes it plausible (Meta: the billing charge of the same amount, count for count in time order; Google and Meta without charge data: the account spend since its previous charge must cover the amount), shown in «Hecho por Vitrina» with the evidence; the rest waits in «Por resolver» (case kind `ads_charge`). Vitrina never holds a Meta or Google token. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "responses": {
          "200": {
            "description": "The connections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAdsConnections"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "profile": true,
                    "last_attempt_at": "2026-10-31T12:30:00.000Z",
                    "last_sync_at": "2026-10-31T12:30:00.000Z",
                    "last_error": null,
                    "platforms": [
                      {
                        "platform": "meta_ads",
                        "label": "Meta Ads",
                        "status": "connected",
                        "accounts": [
                          {
                            "platform": "meta_ads",
                            "ad_account_id": "act_969938652650502",
                            "account_label": "act_969938652650502",
                            "account_name": "connectmarketing-cl",
                            "currency": "CLP",
                            "timezone": "America/Santiago",
                            "connection_status": "connected",
                            "healthy": true,
                            "last_synced_at": "2026-10-31T06:00:58.984Z",
                            "charges_supported": true,
                            "charges_synced_at": "2026-10-31T12:26:04.120Z",
                            "charges_synced": 40,
                            "charges_linked": 38,
                            "charges_unreconciled": 2,
                            "spend_by_month": [
                              {
                                "month": "2026-10",
                                "spend_minor": 545869,
                                "currency": "CLP"
                              }
                            ]
                          }
                        ],
                        "without_connection": "Sin la conexión, los cargos de Meta quedan en Por resolver hasta que reenvíes el recibo.",
                        "connect_path": "/settings/integrations"
                      },
                      {
                        "platform": "google_ads",
                        "label": "Google Ads",
                        "status": "none",
                        "accounts": [],
                        "without_connection": "Sin la conexión, los cargos de Google Ads quedan en Por resolver hasta que reenvíes el recibo.",
                        "connect_path": "/settings/integrations"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/ads-charges": {
      "get": {
        "summary": "Meta billing charges synced, with their link state",
        "description": "Every billing event (charge, refund, chargeback, decline, funding) of the workspace's Meta ad accounts whose day (account time zone) is in [from, to], oldest first, never deduplicated by amount. `linked` = matched to the ads document of a card movement. Amounts in the smallest unit of `currency`. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Charge day (account time zone) from, inclusive. Default: 30 days before `to`."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Charge day to, inclusive. Default: today (Santiago). At most a year."
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The charges",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAdsChargesList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-10-22",
                    "to": "2026-10-22",
                    "items": [
                      {
                        "id": "0193a1b2-0000-7000-8000-000000000001",
                        "platform": "meta_ads",
                        "ad_account_id": "act_969938652650502",
                        "event_type": "ad_account_billing_charge",
                        "event_label": "cargo",
                        "event_time": "2026-10-22T12:14:00.000Z",
                        "local_date": "2026-10-22",
                        "meta_transaction_id": "29140315238986117-29184038214613815",
                        "amount_minor": 4211,
                        "currency": "CLP",
                        "link_state": "linked",
                        "document_id": "0193a1b2-0000-7000-8000-0000000000d1",
                        "movement_id": "0193a1b2-0000-7000-8000-0000000000a1",
                        "linked_at": "2026-10-24T09:00:00.000Z"
                      }
                    ],
                    "totals": {
                      "count": 1,
                      "linked": 1,
                      "unlinked": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/finanzas/ads-charges/sync": {
      "post": {
        "summary": "Read the ad data now and match the card charges",
        "description": "«Sincronizar ahora»: recognise new ad charges among the card movements, read the last 92 days of ad billing and spend, store the charges and link what the evidence supports; then the reconciliation engine and «Por resolver» follow. A failed read is recorded (`read = error`, shown in `GET /finanzas/ads-connections`) and the stored charges still decide what they can. A card movement whose descriptor names Google Ads (`GOOGLE *ADS<customer id>`) or Meta (`FACEBK *<ref>`, `META*…`, `META PLATFORMS…`) becomes a `manual_expense` document of Contraparte «Google Ads» / «Meta Platforms» (`GET /finanzas/documents?origin=ads`); a refund (money in) a nota de crédito. Vitrina links it when the ad data makes it plausible (Meta: the billing charge of the same amount, count for count in time order; Google and Meta without charge data: the account spend since its previous charge must cover the amount), shown in «Hecho por Vitrina» with the evidence; the rest waits in «Por resolver» (case kind `ads_charge`). Vitrina never holds a Meta or Google token. Requires the `finanzas` add-on — without it every Finanzas operation answers `402 ENTITLEMENT_NOT_ACTIVE` (`details.required = [\"finanzas\"]`). Requires `finanzas_admin:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Finanzas"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the run did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FinanzasAdsSyncResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "skipped": false,
                    "read": "ok",
                    "accounts": 1,
                    "charges_new": 3,
                    "recognize": {
                      "recognized": 4,
                      "created": 4,
                      "receipt_first": 0
                    },
                    "match": {
                      "open": 4,
                      "linked": 3,
                      "unconfirmed": 1,
                      "no_connection": 0,
                      "pending": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/evidence-cards": {
      "get": {
        "summary": "Evidence cards for the owner's home (at most three)",
        "description": "Deterministic, no model call. Each card carries `rule`, the proving `number` (`{ value, unit: \"clp\" | \"count\" }`), human-ready Spanish `title` / `label` / `detail` / `cta_label`, a workspace-relative `route`, and `money_at_stake_clp`. Ordered by money at stake, biggest first. A rule whose module is already set up, whose number is zero, or that the caller dismissed (and whose number has not grown in 30 days) is absent: an empty array means render nothing. The numbers are the destination modules' own (boletas, comisiones, conciliación, números comerciales, gastos).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic home"
        ],
        "responses": {
          "200": {
            "description": "The cards",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "cards": [
                      {
                        "rule": "payments_without_boleta",
                        "number": {
                          "value": 1450000,
                          "unit": "clp"
                        },
                        "label": "en pagos sin boleta este mes",
                        "title": "Este mes registraste $1.450.000 en pagos sin boleta",
                        "detail": "12 pagos sin documento tributario. Conecta la emisión electrónica y emite cada boleta desde el mismo pago.",
                        "cta_label": "Activar boletas",
                        "route": "/clinic/boletas?tab=emision",
                        "money_at_stake_clp": 1450000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/evidence-cards/{rule_id}/dismiss": {
      "post": {
        "summary": "Dismiss one evidence card for the calling member",
        "description": "Stored as a per-member preference (no table). The card returns only after 30 days AND only if its number grew since; the number is read server-side at dismissal time, never taken from the caller.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic home"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "payments_without_boleta",
                "commissions_unsettled",
                "payments_unreconciled",
                "accepted_budgets_unscheduled",
                "advertising_spend"
              ]
            },
            "required": true,
            "name": "rule_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The dismissal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "rule": "payments_without_boleta",
                    "dismissed_at": "2026-09-30T15:00:00.000Z",
                    "value": 1450000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/pricing/lists": {
      "get": {
        "summary": "List price lists (aranceles)",
        "description": "Every arancel with its validity window, its sucursal (null = the whole workspace) and how many prestaciones it prices. Retired lists included unless `?active=true` — this screen manages the catalogue.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic price lists",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "38b62be4-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "name": "Arancel particular 2026",
                      "currency": "CLP",
                      "valid_from": "2026-09-22",
                      "valid_to": null,
                      "location_id": null,
                      "is_default": true,
                      "active": true,
                      "created_at": "2026-09-22T22:21:14.611Z",
                      "updated_at": "2026-09-22T22:21:14.611Z",
                      "entry_count": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a price list",
        "description": "A dated arancel. `is_default: true` demotes the previous default in the same scope (tenant-wide, or that sucursal).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "valid_from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "is_default": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Arancel particular 2026",
                "currency": "CLP",
                "is_default": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic price list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "38b62be4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Arancel particular 2026",
                    "currency": "CLP",
                    "valid_from": "2026-09-22",
                    "valid_to": null,
                    "location_id": null,
                    "is_default": true,
                    "active": true,
                    "created_at": "2026-09-22T22:21:14.611Z",
                    "updated_at": "2026-09-22T22:21:14.611Z",
                    "entry_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/pricing/lists/{id}": {
      "delete": {
        "summary": "Delete a price list",
        "description": "Its entries cascade. A convenio pointing at it keeps its row with a null `price_list_id`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a price list",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 3
                  },
                  "valid_from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "is_default": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "valid_from": "2026-01-01"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic price list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "38b62be4-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Arancel particular 2026",
                    "currency": "CLP",
                    "valid_from": "2026-01-01",
                    "valid_to": null,
                    "location_id": null,
                    "is_default": true,
                    "active": true,
                    "created_at": "2026-09-22T22:21:14.611Z",
                    "updated_at": "2026-09-22T22:21:14.627Z",
                    "entry_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Edit a price list\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/pricing/lists/{id}/entries": {
      "get": {
        "summary": "List the prices inside one arancel",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "200": {
            "description": "Clinic price list entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c9f37927-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "price_list_id": "38b62be4-0000-4000-8000-000000000001",
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                      "precio_clp": 28000,
                      "created_at": "2026-09-22T22:21:14.637Z",
                      "updated_at": "2026-09-22T22:21:14.637Z"
                    },
                    {
                      "id": "c9f37927-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "price_list_id": "38b62be4-0000-4000-8000-000000000001",
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                      "precio_clp": 35000,
                      "created_at": "2026-09-22T22:21:14.637Z",
                      "updated_at": "2026-09-22T22:21:14.637Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "List the prices inside one arancel\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "put": {
        "summary": "Set prices inside one arancel",
        "description": "Bulk upsert. `precio_clp: null` REMOVES the entry — \"this list does not price it\" is the absence of a row, and 0 means free (ADR 0058 §2).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "entries": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "precio_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 1000000000
                        }
                      },
                      "required": [
                        "clinic_service_id",
                        "precio_clp"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "entries"
                ]
              },
              "example": {
                "entries": [
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                    "precio_clp": 28000
                  },
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                    "precio_clp": 35000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "{ upserted, removed }",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "upserted": 2,
                    "removed": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/pricing/rules": {
      "get": {
        "summary": "List price rules",
        "description": "The declarative modifiers, in application order (`priority` ascending). Every real-world discount — a convenio override, a previsión price, a campaign, a loyalty tier — is a row here rather than a code branch.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "agreement_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic price rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0f400122-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "15% convenio Empresa Andina",
                      "kind": "convenio",
                      "subject_kind": "all",
                      "subject_id": null,
                      "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                      "prevision": null,
                      "loyalty_tier": null,
                      "professional_id": null,
                      "modifier": "percent_off",
                      "value": 15,
                      "priority": 10,
                      "valid_from": null,
                      "valid_to": null,
                      "active": true,
                      "created_at": "2026-09-22T22:21:14.689Z",
                      "updated_at": "2026-09-22T22:21:14.689Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a price rule",
        "description": "400 `subject_id_required` for a `service`/`category` rule with no subject, and 400 `subject_kind_specialty_not_supported` until `clinic_service.specialty_id` lands — a rule that can never fire is refused rather than accepted and silently ignored.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "convenio",
                      "prevision",
                      "campaign",
                      "pack",
                      "professional",
                      "loyalty",
                      "manual"
                    ]
                  },
                  "subject_kind": {
                    "type": "string",
                    "enum": [
                      "service",
                      "category",
                      "all"
                    ]
                  },
                  "subject_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "agreement_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "prevision": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "loyalty_tier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "0_6",
                      "6_12",
                      "12_plus"
                    ]
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "modifier": {
                    "type": "string",
                    "enum": [
                      "percent_off",
                      "amount_off",
                      "override"
                    ]
                  },
                  "value": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "kind",
                  "modifier",
                  "value"
                ]
              },
              "example": {
                "name": "15% convenio Empresa Andina",
                "kind": "convenio",
                "subject_kind": "all",
                "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                "modifier": "percent_off",
                "value": 15,
                "priority": 10
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic price rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "0f400122-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "15% convenio Empresa Andina",
                    "kind": "convenio",
                    "subject_kind": "all",
                    "subject_id": null,
                    "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                    "prevision": null,
                    "loyalty_tier": null,
                    "professional_id": null,
                    "modifier": "percent_off",
                    "value": 15,
                    "priority": 10,
                    "valid_from": null,
                    "valid_to": null,
                    "active": true,
                    "created_at": "2026-09-22T22:21:14.689Z",
                    "updated_at": "2026-09-22T22:21:14.689Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/pricing/rules/{id}": {
      "delete": {
        "summary": "Delete a price rule",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Delete a price rule\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Edit a price rule",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "convenio",
                      "prevision",
                      "campaign",
                      "pack",
                      "professional",
                      "loyalty",
                      "manual"
                    ]
                  },
                  "subject_kind": {
                    "type": "string",
                    "enum": [
                      "service",
                      "category",
                      "all"
                    ]
                  },
                  "subject_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "agreement_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "prevision": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "loyalty_tier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "0_6",
                      "6_12",
                      "12_plus"
                    ]
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "modifier": {
                    "type": "string",
                    "enum": [
                      "percent_off",
                      "amount_off",
                      "override"
                    ]
                  },
                  "value": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "priority": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic price rule",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0f400122-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "15% convenio Empresa Andina",
                    "kind": "convenio",
                    "subject_kind": "all",
                    "subject_id": null,
                    "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                    "prevision": null,
                    "loyalty_tier": null,
                    "professional_id": null,
                    "modifier": "percent_off",
                    "value": 15,
                    "priority": 20,
                    "valid_from": null,
                    "valid_to": null,
                    "active": true,
                    "created_at": "2026-09-22T22:21:14.689Z",
                    "updated_at": "2026-09-22T22:21:14.701Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Edit a price rule\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/labs": {
      "get": {
        "summary": "List laboratorios",
        "description": "The dental labs the clinic sends work to — imported from Dentalink (`/laboratorios`) by the migration importer, or native. Each row carries the lab's `contact` (address, phone) as the clinic recorded it. Disabled labs included unless `?active=true`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic labs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c0e57121-0000-4000-8000-0000000001ab",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "healthatom",
                      "external_id": "9",
                      "name": "Laboratorio Ejemplo",
                      "contact": {
                        "direccion": "Avenida Ejemplo 456",
                        "telefono": "+56 9 0000 0200"
                      },
                      "active": true,
                      "created_at": "2026-09-23T18:00:00.000Z",
                      "updated_at": "2026-09-23T18:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a laboratorio",
        "description": "A lab the clinic works with that did not arrive from the Dentalink import (`source: \"native\"`). `contact` is the lab's address and phone. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "contact": {
                    "type": "object",
                    "properties": {
                      "direccion": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "telefono": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60
                      }
                    }
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Laboratorio Ejemplo",
                "contact": {
                  "direccion": "Avenida Ejemplo 456",
                  "telefono": "+56 9 0000 0200"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic lab",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000001ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Laboratorio Ejemplo",
                    "contact": {
                      "direccion": "Avenida Ejemplo 456",
                      "telefono": "+56 9 0000 0200"
                    },
                    "active": true,
                    "created_at": "2026-09-29T13:00:00.000Z",
                    "updated_at": "2026-09-29T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/pricing/agreements": {
      "get": {
        "summary": "List convenios",
        "description": "Empresas, colegios and cajas with their validity, their own arancel (if any), who is billed, and how many members they have.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic agreements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c0e57121-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "name": "Convenio Empresa Andina",
                      "organization_name": "Empresa Andina SpA",
                      "organization_rut": "76.543.210-3",
                      "price_list_id": null,
                      "billing_mode": "patient",
                      "valid_from": "2026-01-01",
                      "valid_to": null,
                      "active": true,
                      "notes": null,
                      "created_at": "2026-09-22T22:21:14.651Z",
                      "updated_at": "2026-09-22T22:21:14.651Z",
                      "member_count": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a convenio",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "organization_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "organization_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "price_list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "billing_mode": {
                    "type": "string",
                    "enum": [
                      "patient",
                      "organization",
                      "split"
                    ]
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Convenio Empresa Andina",
                "organization_name": "Empresa Andina SpA",
                "organization_rut": "76.543.210-3",
                "billing_mode": "patient",
                "valid_from": "2026-01-01"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic agreement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Convenio Empresa Andina",
                    "organization_name": "Empresa Andina SpA",
                    "organization_rut": "76.543.210-3",
                    "price_list_id": null,
                    "billing_mode": "patient",
                    "valid_from": "2026-01-01",
                    "valid_to": null,
                    "active": true,
                    "notes": null,
                    "created_at": "2026-09-22T22:21:14.651Z",
                    "updated_at": "2026-09-22T22:21:14.651Z",
                    "member_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "description": "Create a convenio\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/pricing/agreements/{id}": {
      "delete": {
        "summary": "Delete a convenio",
        "description": "Memberships and the rules that name it cascade.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a convenio",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "organization_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "organization_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "price_list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "billing_mode": {
                    "type": "string",
                    "enum": [
                      "patient",
                      "organization",
                      "split"
                    ]
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              },
              "example": {
                "notes": "Presentar credencial de la empresa"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic agreement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Convenio Empresa Andina",
                    "organization_name": "Empresa Andina SpA",
                    "organization_rut": "76.543.210-3",
                    "price_list_id": null,
                    "billing_mode": "patient",
                    "valid_from": "2026-01-01",
                    "valid_to": null,
                    "active": true,
                    "notes": "Presentar credencial de la empresa",
                    "created_at": "2026-09-22T22:21:14.651Z",
                    "updated_at": "2026-09-22T22:21:14.663Z",
                    "member_count": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Edit a convenio\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/pricing/agreements/{id}/members": {
      "get": {
        "summary": "List a convenio’s members",
        "description": "Each membership with the patient’s name and RUT, its member number and its OWN validity window — the window the loyalty tier is measured from.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "200": {
            "description": "Clinic agreement members",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6467baa3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                      "member_number": "EA-0042",
                      "valid_from": null,
                      "valid_to": null,
                      "created_at": "2026-09-22T22:21:14.673Z",
                      "updated_at": "2026-09-22T22:21:14.673Z",
                      "patient_nombre": "María José",
                      "patient_apellidos": "Fuentes Lagos",
                      "patient_rut": "11.111.111-1"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a member to a convenio",
        "description": "Idempotent per (patient, convenio): re-adding re-opens the window on the existing row rather than stacking a second membership.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "member_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "valid_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "valid_to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "clinic_patient_id"
                ]
              },
              "example": {
                "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "member_number": "EA-0042"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic agreement membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "6467baa3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "agreement_id": "c0e57121-0000-4000-8000-000000000001",
                    "member_number": "EA-0042",
                    "valid_from": null,
                    "valid_to": null,
                    "created_at": "2026-09-22T22:21:14.673Z",
                    "updated_at": "2026-09-22T22:21:14.673Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/pricing/members/{id}": {
      "delete": {
        "summary": "Remove a convenio membership",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Remove a convenio membership\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/pricing/quote": {
      "post": {
        "summary": "Quote a prestación, with the explanation",
        "description": "Resolves the price for a prestación on a date, for a patient (through their contact), at a sucursal, with a professional — and returns the ORDERED EXPLANATION: which arancel supplied the base, which professional override replaced it, and every rule that moved it, in the order it was applied. `base_clp: null` with `unpriced_reason: \"no_price_anywhere\"` means nothing prices it — which is not the same as free. `deposit_policy` is a READ of the payments ledger’s own rule (§3.6); this module never writes deposits. A READ that is a POST because its input is a structured body: `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Pricing"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "service_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "clinic_patient_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "agreement_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "prevision": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "loyalty_tier": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "0_6",
                      "6_12",
                      "12_plus"
                    ]
                  },
                  "pack_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "service_id"
                ]
              },
              "example": {
                "service_id": "4cf5bc59-0000-4000-8000-000000000001",
                "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "professional_id": "18342d1b-0000-4000-8000-000000000001",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resolved price with its explanation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "service": {
                      "id": "4cf5bc59-0000-4000-8000-000000000001",
                      "nombre": "Control de ortodoncia",
                      "codigo": "ORT-CTRL",
                      "categoria": "Ortodoncia"
                    },
                    "currency": "CLP",
                    "on": "2026-09-22",
                    "price_list": {
                      "id": "38b62be4-0000-4000-8000-000000000001",
                      "name": "Arancel particular 2026",
                      "valid_from": "2026-01-01",
                      "valid_to": null
                    },
                    "inputs": {
                      "contact_id": null,
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "agreement_ids": [
                        "c0e57121-0000-4000-8000-000000000001"
                      ],
                      "prevision": "Fonasa",
                      "loyalty_tier": "6_12",
                      "pack_id": null
                    },
                    "base_clp": 28000,
                    "final_clp": 23800,
                    "unpriced_reason": null,
                    "explain": [
                      {
                        "order": 1,
                        "source": "price_list",
                        "label": "Arancel particular 2026",
                        "kind": null,
                        "rule_id": null,
                        "modifier": null,
                        "value": null,
                        "amount_before_clp": null,
                        "amount_after_clp": 28000,
                        "delta_clp": 0
                      },
                      {
                        "order": 2,
                        "source": "price_rule",
                        "label": "15% convenio Empresa Andina",
                        "kind": "convenio",
                        "rule_id": "0f400122-0000-4000-8000-000000000001",
                        "modifier": "percent_off",
                        "value": 15,
                        "amount_before_clp": 28000,
                        "amount_after_clp": 23800,
                        "delta_clp": -4200
                      }
                    ],
                    "applied_rule_ids": [
                      "0f400122-0000-4000-8000-000000000001"
                    ],
                    "deposit_policy": {
                      "amount_clp": null,
                      "due_hours": null,
                      "scope": null,
                      "reason": "no_policy"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/services/{id}/professionals": {
      "get": {
        "summary": "Who may perform a prestación",
        "description": "The eligibility set, with each professional’s optional duration and price override. NO ROWS means \"anyone may\" — never \"nobody may\".\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "responses": {
          "200": {
            "description": "Clinic service professionals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "18342d1b-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "duration_minutes": 30,
                      "precio_clp": 28000,
                      "active": true,
                      "created_at": "2026-09-22T22:21:14.591Z",
                      "updated_at": "2026-09-22T22:21:14.591Z",
                      "professional_nombre": "Ana"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set who may perform a prestación",
        "description": "Replaces the WHOLE set in one transaction. An empty array clears it, which means \"anyone may\". 409 `duplicate_professional` when the body names the same professional twice.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "professionals": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "professional_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "duration_minutes": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 1,
                          "maximum": 1440
                        },
                        "precio_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 1000000000
                        },
                        "active": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "professional_id"
                      ]
                    },
                    "maxItems": 200
                  }
                },
                "required": [
                  "professionals"
                ]
              },
              "example": {
                "professionals": [
                  {
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "duration_minutes": 30,
                    "precio_clp": 28000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic service professionals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "18342d1b-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "duration_minutes": 30,
                      "precio_clp": 28000,
                      "active": true,
                      "created_at": "2026-09-22T22:21:14.591Z",
                      "updated_at": "2026-09-22T22:21:14.591Z",
                      "professional_nombre": "Ana"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/labs/{id}": {
      "patch": {
        "summary": "Edit or deactivate a laboratorio",
        "description": "Rename it, change its contact, or `active: false` to hide it from the order form. A lab is deactivated, never deleted: its orders keep naming it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "contact": {
                    "type": "object",
                    "properties": {
                      "direccion": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "telefono": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60
                      }
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic lab",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000001ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Laboratorio Ejemplo",
                    "contact": {
                      "direccion": "Avenida Ejemplo 456",
                      "telefono": "+56 9 0000 0200"
                    },
                    "active": false,
                    "created_at": "2026-09-29T13:00:00.000Z",
                    "updated_at": "2026-09-29T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lab-work-types": {
      "get": {
        "summary": "List the labs' price lists",
        "description": "What each lab charges per kind of work («Corona de zirconio», $95.000). `?lab_id=` narrows to one lab; `?active=true` is what the order form offers. The cost here is the DEFAULT a new order copies.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lab_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Lab work types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c0e57121-0000-4000-8000-0000000002ab",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                      "source": "native",
                      "external_id": null,
                      "name": "Corona de zirconio",
                      "cost_clp": 95000,
                      "active": true,
                      "created_at": "2026-09-29T13:01:00.000Z",
                      "updated_at": "2026-09-29T13:01:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a line to a lab's price list",
        "description": "One name per lab, case-insensitive — a duplicate answers 409. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_lab_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "cost_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  }
                },
                "required": [
                  "clinic_lab_id",
                  "name",
                  "cost_clp"
                ]
              },
              "example": {
                "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                "name": "Corona de zirconio",
                "cost_clp": 95000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lab work type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000002ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                    "source": "native",
                    "external_id": null,
                    "name": "Corona de zirconio",
                    "cost_clp": 95000,
                    "active": true,
                    "created_at": "2026-09-29T13:01:00.000Z",
                    "updated_at": "2026-09-29T13:01:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/lab-work-types/{id}": {
      "patch": {
        "summary": "Re-price or retire a line of a lab's price list",
        "description": "Changing `cost_clp` affects orders created FROM NOW ON; an order already created keeps the cost it was created with.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "cost_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "cost_clp": 98000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lab work type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000002ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                    "source": "native",
                    "external_id": null,
                    "name": "Corona de zirconio",
                    "cost_clp": 98000,
                    "active": true,
                    "created_at": "2026-09-29T13:01:00.000Z",
                    "updated_at": "2026-09-29T13:01:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/lab-orders": {
      "get": {
        "summary": "List lab orders",
        "description": "Every order with its lab, patient, treatment and professional. Filter by `lab_id`, `status`, or `open=true` (to send + sent — the work still in flight); `treatment_plan_item_id`, `treatment_plan_id` and `patient_id` narrow to one treatment, plan or ficha. Work in flight sorts first, oldest first. `counts` carries the per-status totals for the same lab/patient filter, ignoring `status`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lab_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "to_send",
                "sent",
                "received",
                "cancelled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "open",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "treatment_plan_item_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "treatment_plan_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Lab orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "c0e57121-0000-4000-8000-0000000003ab",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "treatment_plan_item_id": "b7b7b7b7-0000-4000-8000-000000000011",
                        "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                        "work_type_id": "c0e57121-0000-4000-8000-0000000002ab",
                        "work_type": "Corona de zirconio",
                        "cost_clp": 95000,
                        "status": "sent",
                        "sent_on": "2026-09-29",
                        "received_on": null,
                        "notes": "Color A2",
                        "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                        "created_at": "2026-09-29T13:05:00.000Z",
                        "updated_at": "2026-09-29T15:20:00.000Z",
                        "lab_name": "Laboratorio Ejemplo",
                        "treatment_plan_id": "b7b7b7b7-0000-4000-8000-000000000001",
                        "treatment_plan_name": "Rehabilitación sector posterior",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "patient_name": "María José Fuentes Lagos",
                        "service_name": "Corona cerámica",
                        "professional_name": "Camila Rojas"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50,
                    "counts": {
                      "to_send": 0,
                      "sent": 1,
                      "received": 0,
                      "cancelled": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Send work to a lab for a treatment",
        "description": "Created from a treatment-plan item. With `work_type_id` (a line of the lab's price list) the name and cost default from the list; `work_type` / `cost_clp` override them, and are required without it. `status` defaults to `to_send`; `sent` / `received` without a date are stamped with today in the clinic's calendar. A cancelled treatment or a deactivated lab answers 400.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "treatment_plan_item_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "clinic_lab_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "work_type_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "work_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "cost_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "to_send",
                      "sent",
                      "received",
                      "cancelled"
                    ]
                  },
                  "sent_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "received_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "required": [
                  "treatment_plan_item_id",
                  "clinic_lab_id"
                ]
              },
              "example": {
                "treatment_plan_item_id": "b7b7b7b7-0000-4000-8000-000000000011",
                "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                "work_type_id": "c0e57121-0000-4000-8000-0000000002ab",
                "notes": "Color A2"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Lab order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000003ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "treatment_plan_item_id": "b7b7b7b7-0000-4000-8000-000000000011",
                    "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                    "work_type_id": "c0e57121-0000-4000-8000-0000000002ab",
                    "work_type": "Corona de zirconio",
                    "cost_clp": 95000,
                    "status": "to_send",
                    "sent_on": null,
                    "received_on": null,
                    "notes": "Color A2",
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-09-29T13:05:00.000Z",
                    "updated_at": "2026-09-29T15:20:00.000Z",
                    "lab_name": "Laboratorio Ejemplo",
                    "treatment_plan_id": "b7b7b7b7-0000-4000-8000-000000000001",
                    "treatment_plan_name": "Rehabilitación sector posterior",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "María José Fuentes Lagos",
                    "service_name": "Corona cerámica",
                    "professional_name": "Camila Rojas"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/lab-orders/{id}": {
      "get": {
        "summary": "One lab order",
        "description": "The order with the same names the list carries.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Lab order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000003ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "treatment_plan_item_id": "b7b7b7b7-0000-4000-8000-000000000011",
                    "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                    "work_type_id": "c0e57121-0000-4000-8000-0000000002ab",
                    "work_type": "Corona de zirconio",
                    "cost_clp": 95000,
                    "status": "sent",
                    "sent_on": "2026-09-29",
                    "received_on": null,
                    "notes": "Color A2",
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-09-29T13:05:00.000Z",
                    "updated_at": "2026-09-29T15:20:00.000Z",
                    "lab_name": "Laboratorio Ejemplo",
                    "treatment_plan_id": "b7b7b7b7-0000-4000-8000-000000000001",
                    "treatment_plan_name": "Rehabilitación sector posterior",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "María José Fuentes Lagos",
                    "service_name": "Corona cerámica",
                    "professional_name": "Camila Rojas"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Move a lab order along, or correct it",
        "description": "`status: \"sent\"` / `\"received\"` stamps today when no date is given; `\"to_send\"` clears both dates; `\"cancelled\"` keeps them and stops the order counting toward the treatment's lab cost. Choosing another `work_type_id` re-defaults the name and cost unless the same request sends its own. A received date before the sent date answers 400.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Lab Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_lab_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "work_type_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "work_type": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "cost_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "to_send",
                      "sent",
                      "received",
                      "cancelled"
                    ]
                  },
                  "sent_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "received_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                }
              },
              "example": {
                "status": "sent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lab order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c0e57121-0000-4000-8000-0000000003ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "treatment_plan_item_id": "b7b7b7b7-0000-4000-8000-000000000011",
                    "clinic_lab_id": "c0e57121-0000-4000-8000-0000000001ab",
                    "work_type_id": "c0e57121-0000-4000-8000-0000000002ab",
                    "work_type": "Corona de zirconio",
                    "cost_clp": 95000,
                    "status": "sent",
                    "sent_on": "2026-09-29",
                    "received_on": null,
                    "notes": "Color A2",
                    "created_by": "5e5e5e5e-0000-4000-8000-000000000001",
                    "created_at": "2026-09-29T13:05:00.000Z",
                    "updated_at": "2026-09-29T15:20:00.000Z",
                    "lab_name": "Laboratorio Ejemplo",
                    "treatment_plan_id": "b7b7b7b7-0000-4000-8000-000000000001",
                    "treatment_plan_name": "Rehabilitación sector posterior",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "patient_name": "María José Fuentes Lagos",
                    "service_name": "Corona cerámica",
                    "professional_name": "Camila Rojas"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/charts": {
      "get": {
        "summary": "The specialty charts a patient has",
        "description": "Every chart of one patient — odontograma, periodontograma, mapa facial, mapa corporal — with its notation and the version it is currently on. No state: the picture comes from the per-kind endpoint below.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Charts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "b82b40b8-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "kind": "odontograma",
                        "notation": "fdi",
                        "current_version": 1,
                        "current_version_id": "165a182b-0000-4000-8000-000000000001",
                        "created_at": "2026-09-22T22:21:17.537Z",
                        "updated_at": "2026-09-22T22:21:17.539Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/chart-findings": {
      "get": {
        "summary": "Everything found on this patient, across every chart",
        "description": "The findings of all this patient’s charts, newest first, optionally filtered to `active` or `treated`. What a presupuesto screen asks for: each row carries `tooth_or_zone`, the surfaces for a tooth, and the `treatment_plan_item_id` it already produced (null = not quoted yet).\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "treated"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Findings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "e29e8d94-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                        "tooth_or_zone": "26",
                        "surfaces": [
                          "O"
                        ],
                        "code": "caries",
                        "label": "Caries oclusal",
                        "note": null,
                        "status": "active",
                        "first_seen_version": 1,
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "treatment_plan_item_id": null,
                        "clinic_appointment_service_id": null,
                        "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "treated_at": null,
                        "treated_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.577Z",
                        "updated_at": "2026-09-22T22:21:17.577Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/charts/{kind}": {
      "get": {
        "summary": "One chart: its current state, its findings and its vocabulary",
        "description": "The chart as it stands, plus the findings painted on it and the vocabulary the client should render (`notation`, the notations this kind allows, and the zone list for a face/body map). Creates the chart row on first look — `current: null` then means \"never filled in\", which is NOT the same as \"every tooth is healthy\" and must not be rendered as an empty chart. A version carried across from the clinic's previous system by an instructed migration run has `imported_from` set (`healthatom`) and `imported_as_of` = the date that system showed («Importado de Dentalink · <fecha>»); it is read-only like every version, and a save draws the next version on top of it.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "odontograma",
                "periodontograma",
                "mapa_facial",
                "mapa_corporal"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Chart",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "chart": {
                      "id": "b82b40b8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "kind": "odontograma",
                      "notation": "fdi",
                      "current_version": 1,
                      "current_version_id": "165a182b-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T22:21:17.537Z",
                      "updated_at": "2026-09-22T22:21:17.539Z"
                    },
                    "current": {
                      "id": "165a182b-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                      "version": 1,
                      "state": {
                        "note": "Resto de las piezas sin hallazgos",
                        "entries": {
                          "26": {
                            "items": [
                              {
                                "code": "caries",
                                "note": null,
                                "unit": null,
                                "label": "Caries oclusal",
                                "quantity": null,
                                "surfaces": [
                                  "O"
                                ]
                              }
                            ]
                          }
                        }
                      },
                      "notation": "fdi",
                      "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                      "author_user_id": "11111111-0000-4000-8000-000000000001",
                      "author_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                      "summary": "Odontograma inicial",
                      "created_at": "2026-09-22T22:21:17.539Z"
                    },
                    "findings": [],
                    "vocabulary": {
                      "notation": "fdi",
                      "allowed_notations": [
                        "fdi",
                        "palmer",
                        "universal"
                      ],
                      "zones": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/charts/{kind}/versions": {
      "post": {
        "summary": "Save the chart — an edit creates a version",
        "description": "Saves the WHOLE state as a new immutable version (the database refuses an update of a saved one) and records any findings the same edit declared, in one transaction. `expected_version` is an optimistic lock: pass the version the editor had on screen and a save made after somebody else’s is refused with a 409 instead of overwriting it. Keys are validated against the chart’s own notation — FDI tooth numbers, Palmer, Universal, or the zone vocabulary of a mapa facial / corporal — never against a global one.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "odontograma",
                "periodontograma",
                "mapa_facial",
                "mapa_corporal"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "state": {
                    "type": "object",
                    "properties": {
                      "entries": {
                        "type": "object",
                        "additionalProperties": {
                          "type": "object",
                          "properties": {
                            "items": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "code": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 80
                                  },
                                  "surfaces": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "minLength": 1,
                                      "maxLength": 2
                                    },
                                    "maxItems": 5
                                  },
                                  "label": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "maxLength": 200
                                  },
                                  "note": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "maxLength": 2000
                                  },
                                  "quantity": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "unit": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "maxLength": 20
                                  }
                                },
                                "required": [
                                  "code"
                                ]
                              },
                              "maxItems": 40
                            }
                          },
                          "required": [
                            "items"
                          ]
                        }
                      },
                      "note": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 4000
                      }
                    },
                    "required": [
                      "entries"
                    ]
                  },
                  "notation": {
                    "type": "string",
                    "enum": [
                      "fdi",
                      "palmer",
                      "universal",
                      "zona"
                    ]
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "summary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 400
                  },
                  "expected_version": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "findings": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "tooth_or_zone": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 40
                        },
                        "code": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 80
                        },
                        "surfaces": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 2
                          },
                          "maxItems": 5
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 200
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 2000
                        }
                      },
                      "required": [
                        "tooth_or_zone",
                        "code"
                      ]
                    },
                    "maxItems": 60
                  }
                },
                "required": [
                  "state"
                ]
              },
              "example": {
                "notation": "fdi",
                "summary": "Odontograma inicial",
                "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                "state": {
                  "entries": {
                    "26": {
                      "items": [
                        {
                          "code": "caries",
                          "surfaces": [
                            "O"
                          ],
                          "label": "Caries oclusal"
                        }
                      ]
                    }
                  },
                  "note": "Resto de las piezas sin hallazgos"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Saved version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "chart": {
                      "id": "b82b40b8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "kind": "odontograma",
                      "notation": "fdi",
                      "current_version": 1,
                      "current_version_id": "165a182b-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T22:21:17.537Z",
                      "updated_at": "2026-09-22T22:21:17.539Z"
                    },
                    "version": {
                      "id": "165a182b-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                      "version": 1,
                      "state": {
                        "note": "Resto de las piezas sin hallazgos",
                        "entries": {
                          "26": {
                            "items": [
                              {
                                "code": "caries",
                                "note": null,
                                "unit": null,
                                "label": "Caries oclusal",
                                "quantity": null,
                                "surfaces": [
                                  "O"
                                ]
                              }
                            ]
                          }
                        }
                      },
                      "notation": "fdi",
                      "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                      "author_user_id": "11111111-0000-4000-8000-000000000001",
                      "author_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                      "summary": "Odontograma inicial",
                      "created_at": "2026-09-22T22:21:17.539Z"
                    },
                    "findings": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/charts/{chartId}/versions": {
      "get": {
        "summary": "The chart’s version timeline",
        "description": "Every saved state of one chart, newest first, with its author, the atención it was saved in and the summary its author wrote. The client diffs two versions to show what changed. Imported versions (the history carried across from Dentalink) are the oldest — the end of this newest-first list — and carry `imported_from` / `imported_as_of`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "chartId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "165a182b-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                        "version": 1,
                        "state": {
                          "note": "Resto de las piezas sin hallazgos",
                          "entries": {
                            "26": {
                              "items": [
                                {
                                  "code": "caries",
                                  "note": null,
                                  "unit": null,
                                  "label": "Caries oclusal",
                                  "quantity": null,
                                  "surfaces": [
                                    "O"
                                  ]
                                }
                              ]
                            }
                          }
                        },
                        "notation": "fdi",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "author_user_id": "11111111-0000-4000-8000-000000000001",
                        "author_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                        "summary": "Odontograma inicial",
                        "created_at": "2026-09-22T22:21:17.539Z"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50,
                    "chart": {
                      "id": "b82b40b8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "kind": "odontograma",
                      "notation": "fdi",
                      "current_version": 1,
                      "current_version_id": "165a182b-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T22:21:17.537Z",
                      "updated_at": "2026-09-22T22:21:17.539Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/charts/{chartId}/versions/{version}": {
      "get": {
        "summary": "One past state of a chart",
        "description": "The state as it was at that version — what the diff and the \"ver esta versión\" control read. Immutable: this row is what somebody signed.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "chartId",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "165a182b-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "version": 1,
                    "state": {
                      "note": "Resto de las piezas sin hallazgos",
                      "entries": {
                        "26": {
                          "items": [
                            {
                              "code": "caries",
                              "note": null,
                              "unit": null,
                              "label": "Caries oclusal",
                              "quantity": null,
                              "surfaces": [
                                "O"
                              ]
                            }
                          ]
                        }
                      }
                    },
                    "notation": "fdi",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                    "summary": "Odontograma inicial",
                    "created_at": "2026-09-22T22:21:17.539Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/charts/{chartId}/findings": {
      "get": {
        "summary": "The findings painted on one chart",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "chartId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "treated"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Findings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "e29e8d94-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                        "tooth_or_zone": "26",
                        "surfaces": [
                          "O"
                        ],
                        "code": "caries",
                        "label": "Caries oclusal",
                        "note": null,
                        "status": "active",
                        "first_seen_version": 1,
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "treatment_plan_item_id": null,
                        "clinic_appointment_service_id": null,
                        "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "treated_at": null,
                        "treated_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.577Z",
                        "updated_at": "2026-09-22T22:21:17.577Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "description": "The findings painted on one chart\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it."
      },
      "post": {
        "summary": "Record one finding without redrawing the chart",
        "description": "The «a tratar» click: one tooth or zone, one code, optionally the surfaces. The target is validated against the chart’s notation.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "chartId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tooth_or_zone": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40
                  },
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "surfaces": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 2
                    },
                    "maxItems": 5
                  },
                  "label": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "tooth_or_zone",
                  "code"
                ]
              },
              "example": {
                "tooth_or_zone": "26",
                "code": "caries",
                "surfaces": [
                  "O"
                ],
                "label": "Caries oclusal",
                "encounter_id": "434a1a10-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Finding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e29e8d94-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "tooth_or_zone": "26",
                    "surfaces": [
                      "O"
                    ],
                    "code": "caries",
                    "label": "Caries oclusal",
                    "note": null,
                    "status": "active",
                    "first_seen_version": 1,
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "treatment_plan_item_id": null,
                    "clinic_appointment_service_id": null,
                    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "treated_at": null,
                    "treated_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.577Z",
                    "updated_at": "2026-09-22T22:21:17.577Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/chart-findings/{findingId}/resolve": {
      "post": {
        "summary": "Mark a finding treated",
        "description": "The row stays: a treated caries is history, not noise, and the chart renders it differently rather than forgetting it.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "findingId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e29e8d94-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "tooth_or_zone": "26",
                    "surfaces": [
                      "O"
                    ],
                    "code": "caries",
                    "label": "Caries oclusal",
                    "note": null,
                    "status": "treated",
                    "first_seen_version": 1,
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "treatment_plan_item_id": null,
                    "clinic_appointment_service_id": null,
                    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "treated_at": "2026-09-22T22:21:17.614Z",
                    "treated_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:17.577Z",
                    "updated_at": "2026-09-22T22:21:17.613Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/chart-findings/{findingId}/reopen": {
      "post": {
        "summary": "Undo «tratado» on a finding",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "findingId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Finding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e29e8d94-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "tooth_or_zone": "26",
                    "surfaces": [
                      "O"
                    ],
                    "code": "caries",
                    "label": "Caries oclusal",
                    "note": null,
                    "status": "active",
                    "first_seen_version": 1,
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "treatment_plan_item_id": null,
                    "clinic_appointment_service_id": null,
                    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "treated_at": null,
                    "treated_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.577Z",
                    "updated_at": "2026-09-22T22:21:17.622Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "description": "Undo «tratado» on a finding\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it."
      }
    },
    "/clinic/chart-findings/{findingId}/plan-item": {
      "get": {
        "summary": "The presupuesto line this finding would become",
        "description": "The draft a treatment-plan item is built from, carrying the tooth or zone so the fact survives to `clinic_appointment_service.tooth_or_zone`. The plan item itself belongs to the presupuestos module (T-33); this endpoint is the seam between the two.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "findingId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Plan item draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "chart_kind": "odontograma",
                    "notation": "fdi",
                    "finding_id": "e29e8d94-0000-4000-8000-000000000001",
                    "tooth_or_zone": "26",
                    "surfaces": [
                      "O"
                    ],
                    "code": "caries",
                    "label": "Caries oclusal",
                    "note": null,
                    "treatment_plan_item_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Link a finding to the plan item (or cita line) it produced",
        "description": "Records the `clinic_treatment_plan_item` a finding became, so the chart can say «ya está en el presupuesto» instead of offering the same tooth twice, and/or the `clinic_appointment_service` it was finally performed on. Pass null to unlink.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "findingId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "treatment_plan_item_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "clinic_appointment_service_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "treatment_plan_item_id": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Finding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e29e8d94-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "chart_id": "b82b40b8-0000-4000-8000-000000000001",
                    "tooth_or_zone": "26",
                    "surfaces": [
                      "O"
                    ],
                    "code": "caries",
                    "label": "Caries oclusal",
                    "note": null,
                    "status": "active",
                    "first_seen_version": 1,
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "treatment_plan_item_id": null,
                    "clinic_appointment_service_id": null,
                    "created_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "treated_at": null,
                    "treated_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.577Z",
                    "updated_at": "2026-09-22T22:21:17.604Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/budgets": {
      "get": {
        "summary": "List presupuestos (the conversion funnel)",
        "description": "Quotes newest first, with their lines, the patient and the professional. `?live=true` is the working set (borrador + enviado); `?status=` picks one column. A voided presupuesto stays listable — a correlative series with a hole in it cannot be audited. Every row carries its LINEAGE (`lineage_id`, `version`, `lineage_size`, `reissued_from_id`, `reissued_by_id`) and the patient's «visto» (`first_viewed_at`, `last_viewed_at`). `?group_by=lineage` returns ONE row per lineage — its latest version that is neither a draft nor voided (a draft only when the lineage has nothing else) — and the other filters then read that version; `?lineage_id=` lists one lineage's versions.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "sent",
                "accepted",
                "rejected",
                "expired",
                "voided"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "live",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "treatment_plan_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lineage_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "lineage"
              ]
            },
            "required": false,
            "name": "group_by",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic budgets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "81e4a552-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "display_seq": 1,
                        "display_id": "E-1",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "lead_id": null,
                        "lineage_id": "81e4a552-0000-4000-8000-000000000001",
                        "reissued_from_id": null,
                        "conversation_id": null,
                        "professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "price_list_id": null,
                        "agreement_id": null,
                        "legal_entity_id": null,
                        "status": "draft",
                        "subtotal_clp": 147000,
                        "discount_clp": 5000,
                        "total_clp": 142000,
                        "tax_treatment": "afecto",
                        "valid_until": "2026-12-31",
                        "installments": 3,
                        "notes": "Incluye los cuatro controles del primer semestre",
                        "obligation_group_id": null,
                        "rendered_document_id": null,
                        "created_by_user_id": null,
                        "sent_at": null,
                        "accepted_at": null,
                        "accepted_via": null,
                        "rejected_at": null,
                        "rejected_reason": null,
                        "expired_at": null,
                        "voided_at": null,
                        "voided_by": null,
                        "voided_reason": null,
                        "created_at": "2026-09-22T22:21:15.369Z",
                        "updated_at": "2026-09-22T22:21:15.412Z",
                        "patient_name": "María José Fuentes Lagos",
                        "professional_name": "Ana Rojas Vidal",
                        "items": [
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000004",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "81e4a552-0000-4000-8000-000000000001",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                            "pack_id": null,
                            "label": "Control de ortodoncia",
                            "quantity": 4,
                            "unit_price_clp": 28000,
                            "discount_clp": 0,
                            "total_clp": 112000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 28000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 1,
                            "note": null,
                            "sort_order": 0,
                            "created_at": "2026-09-22T22:21:15.392Z",
                            "updated_at": "2026-09-22T22:21:15.392Z",
                            "service_name": "Control de ortodoncia"
                          },
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000005",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "81e4a552-0000-4000-8000-000000000001",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                            "pack_id": null,
                            "label": "Limpieza dental",
                            "quantity": 1,
                            "unit_price_clp": 35000,
                            "discount_clp": 5000,
                            "total_clp": 30000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 35000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 2,
                            "note": null,
                            "sort_order": 1,
                            "created_at": "2026-09-22T22:21:15.410Z",
                            "updated_at": "2026-09-22T22:21:15.410Z",
                            "service_name": "Limpieza dental"
                          }
                        ],
                        "is_live": true,
                        "days_left": 100
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a presupuesto (draft)",
        "description": "Allocates the `E-` folio and freezes each line's price through the quote resolver — the arancel, the convenio and the professional surcharge are read ONCE and stored with the explanation that produced them. `unit_price_clp` on a line is the human override; omit it and the resolver decides. Always lands in `draft`. With `reissued_from_id` it is a NEW VERSION of that presupuesto and follows exactly the rules of `POST /clinic/budgets/{id}/supersede`: the draft copies the predecessor's header and lines (accepted lines carried), the header fields given override the copied ones, and `items` are added after the copied lines.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "price_list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "agreement_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "treatment_plan_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "reissued_from_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado"
                    ]
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "installments": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 48
                  },
                  "installments_first_due": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "pack_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "unit_price_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 0,
                          "maximum": 9999999999
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999,
                          "default": 1
                        },
                        "discount_clp": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 9999999999,
                          "default": 0
                        },
                        "tax_treatment": {
                          "type": "string",
                          "enum": [
                            "afecto",
                            "exento",
                            "no_gravado"
                          ]
                        },
                        "phase": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 99,
                          "default": 1
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 500
                        },
                        "label": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 200
                        }
                      },
                      "additionalProperties": false
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "clinic_patient_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "professional_id": "18342d1b-0000-4000-8000-000000000001",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                "valid_until": "2026-12-31",
                "installments": 3,
                "notes": "Incluye los cuatro controles del primer semestre",
                "items": [
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                    "quantity": 4,
                    "phase": 1
                  },
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                    "quantity": 1,
                    "phase": 2,
                    "discount_clp": 5000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "81e4a552-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 1,
                    "display_id": "E-1",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
                    "reissued_from_id": null,
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "draft",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles del primer semestre",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": null,
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": null,
                    "rejected_reason": null,
                    "expired_at": null,
                    "voided_at": null,
                    "voided_by": null,
                    "voided_reason": null,
                    "created_at": "2026-09-22T22:21:15.369Z",
                    "updated_at": "2026-09-22T22:21:15.412Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000004",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.392Z",
                        "updated_at": "2026-09-22T22:21:15.392Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000005",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.410Z",
                        "updated_at": "2026-09-22T22:21:15.410Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": true,
                    "days_left": 100
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/budgets/summary": {
      "get": {
        "summary": "The COMERCIAL badge and funnel, counted by lineage",
        "description": "Counts LINEAGES, not documents: a proposal re-issued three times is one proposal, read at its latest version that is neither a draft nor voided. `sent_and_live` is the badge and counts on `valid_until` rather than on `status`, so a quote that lapsed twelve minutes ago is already out — the expiry sweep runs on a tick and the badge must not tell the clinic to chase a dead number. `lineages` / `by_status` are the funnel.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "responses": {
          "200": {
            "description": "Budget summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "sent_and_live": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/mirror-plans": {
      "get": {
        "summary": "A patient's mirrored Dentalink plans, for «Crear desde Dentalink»",
        "description": "The patient's plans in the espejo de ingresos (a Dentalink clinic's tratamientos, copied read-only), newest first, each with `lines_count`, `unmapped_count` (lines whose prestación is not in the local catalogue yet — the plan cannot be photographed until the catalog importer runs) and `already_budgeted` / `budget_id` (a non-voided presupuesto already photographs it). `available: false` means the workspace has no enabled Dentalink mirror, and `items` is empty.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "patient_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Mirrored plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "available": true,
                    "items": [
                      {
                        "id": "6d2c1f0a-0000-4000-8000-000000000001",
                        "external_id": 90412,
                        "fecha": "2026-09-10",
                        "finalizado": false,
                        "bloqueado": false,
                        "total": 150000,
                        "deuda": 150000,
                        "clinic_professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "professional_name": "Camila Rojas",
                        "lines_count": 2,
                        "unmapped_count": 0,
                        "already_budgeted": false,
                        "budget_id": null
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/from-mirror-plan": {
      "post": {
        "summary": "Create a presupuesto (draft) from a mirrored Dentalink plan",
        "description": "On a Dentalink clinic the plan is born in Dentalink; this composes the presupuesto FROM its mirrored copy in one action. Every line of the plan becomes a line at Dentalink's net price (after its descuentos), frozen with an explanation that names the source; the plan's professional becomes the presupuesto's (per-line performers are not kept). The response carries `mirror: {plan_id, snapshot_at, changed_at}`. When the plan later changes in Dentalink — a line added, removed or repriced, even at the same total — `mirror.changed_at` is set and `clinic_budget.mirror_changed` fires; `POST /clinic/budgets/{id}/supersede` then writes a new version from the live plan. 409 `MIRROR_LINES_UNMAPPED` lists the prestaciones missing from the local catalogue (`details.fix` names the importer); 409 with `details.reason` for a plan whose patient is not linked, a plan with no lines yet, or one another presupuesto already photographs. Vitrina never writes to Dentalink.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mirror_plan_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "clinic_patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado"
                    ]
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "installments": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 48
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "mirror_plan_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "mirror_plan_id": "6d2c1f0a-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "458363ad-0000-4000-8000-000000000009",
                    "display_id": "E-52",
                    "status": "draft",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "lineage_id": "458363ad-0000-4000-8000-000000000009",
                    "reissued_from_id": null,
                    "subtotal_clp": 150000,
                    "discount_clp": 0,
                    "total_clp": 150000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-10-23",
                    "mirror": {
                      "plan_id": "6d2c1f0a-0000-4000-8000-000000000001",
                      "snapshot_at": "2026-09-23T14:02:11.000Z",
                      "changed_at": null
                    },
                    "items": [
                      {
                        "id": "a1c0ffee-0000-4000-8000-000000000001",
                        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000001",
                        "label": "Resina compuesta",
                        "quantity": 1,
                        "unit_price_clp": 100000,
                        "discount_clp": 0,
                        "total_clp": 100000,
                        "price_explain": [
                          {
                            "order": 1,
                            "source": "mirror_snapshot",
                            "label": "Precio del tratamiento en Dentalink",
                            "amount_before_clp": null,
                            "amount_after_clp": 120000,
                            "delta_clp": 0,
                            "external_id": 1,
                            "external_plan": 90412
                          },
                          {
                            "order": 2,
                            "source": "mirror_snapshot",
                            "label": "Descuento registrado en Dentalink",
                            "amount_before_clp": 120000,
                            "amount_after_clp": 100000,
                            "delta_clp": -20000,
                            "external_id": 1,
                            "external_plan": 90412
                          }
                        ]
                      },
                      {
                        "id": "a1c0ffee-0000-4000-8000-000000000002",
                        "clinic_service_id": "5e5e5e5e-0000-4000-8000-000000000002",
                        "label": "Sellante",
                        "quantity": 1,
                        "unit_price_clp": 50000,
                        "discount_clp": 0,
                        "total_clp": 50000,
                        "price_explain": [
                          {
                            "order": 1,
                            "source": "mirror_snapshot",
                            "label": "Precio del tratamiento en Dentalink",
                            "amount_before_clp": null,
                            "amount_after_clp": 50000,
                            "delta_clp": 0,
                            "external_id": 2,
                            "external_plan": 90412
                          }
                        ]
                      }
                    ],
                    "version": 1,
                    "lineage_size": 1,
                    "reissued_by_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/budgets/{id}": {
      "get": {
        "summary": "One presupuesto, by UUID or folio",
        "description": "`{id}` accepts a UUID or the folio (`E-47`).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^E-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "81e4a552-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 1,
                    "display_id": "E-1",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
                    "reissued_from_id": null,
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "draft",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles del primer semestre",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": null,
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": null,
                    "rejected_reason": null,
                    "expired_at": null,
                    "voided_at": null,
                    "voided_by": null,
                    "voided_reason": null,
                    "created_at": "2026-09-22T22:21:15.369Z",
                    "updated_at": "2026-09-22T22:21:15.412Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000004",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.392Z",
                        "updated_at": "2026-09-22T22:21:15.392Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000005",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.410Z",
                        "updated_at": "2026-09-22T22:21:15.410Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": true,
                    "days_left": 100
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Throw away a DRAFT",
        "description": "Only a draft. Anything that has been sent is voided (`POST /clinic/budgets/{id}/void`) and keeps its folio — ADR 0058 §10.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a DRAFT presupuesto",
        "description": "Header fields only, and only while the quote is a draft — a sent one answers 409 rather than silently ignoring the patch. Moving the sucursal moves the razón social with it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "price_list_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "agreement_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "treatment_plan_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado"
                    ]
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "installments": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 48
                  },
                  "installments_first_due": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "notes": "Incluye los cuatro controles y la limpieza inicial"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "81e4a552-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 1,
                    "display_id": "E-1",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "lineage_id": "81e4a552-0000-4000-8000-000000000001",
                    "reissued_from_id": null,
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "draft",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles y la limpieza inicial",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": null,
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": null,
                    "rejected_reason": null,
                    "expired_at": null,
                    "voided_at": null,
                    "voided_by": null,
                    "voided_reason": null,
                    "created_at": "2026-09-22T22:21:15.369Z",
                    "updated_at": "2026-09-22T22:21:15.445Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000004",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.392Z",
                        "updated_at": "2026-09-22T22:21:15.392Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000005",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "81e4a552-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.410Z",
                        "updated_at": "2026-09-22T22:21:15.410Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": true,
                    "days_left": 100
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/lineage": {
      "get": {
        "summary": "Every version of one proposal",
        "description": "The budget's whole lineage (#2446), `version` ascending — the first presupuesto handed over plus each one that superseded it. `{id}` may name any member, by UUID or folio.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^E-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic budget lineage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "81e4a552-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "display_seq": 2,
                        "display_id": "E-2",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                        "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                        "reissued_from_id": null,
                        "conversation_id": null,
                        "professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "price_list_id": null,
                        "agreement_id": null,
                        "legal_entity_id": null,
                        "status": "accepted",
                        "subtotal_clp": 147000,
                        "discount_clp": 5000,
                        "total_clp": 142000,
                        "tax_treatment": "afecto",
                        "valid_until": "2026-12-31",
                        "installments": 3,
                        "notes": "Incluye los cuatro controles del primer semestre",
                        "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
                        "rendered_document_id": null,
                        "created_by_user_id": null,
                        "sent_at": "2026-09-23T01:07:07.750Z",
                        "accepted_at": "2026-09-23T01:07:07.912Z",
                        "accepted_via": "staff",
                        "rejected_at": null,
                        "rejected_reason": null,
                        "expired_at": null,
                        "voided_at": null,
                        "voided_by": null,
                        "voided_reason": null,
                        "first_viewed_at": null,
                        "last_viewed_at": null,
                        "created_at": "2026-09-23T01:07:07.683Z",
                        "updated_at": "2026-09-23T01:07:07.941Z",
                        "patient_name": "María José Fuentes Lagos",
                        "professional_name": "Ana Rojas Vidal",
                        "items": [
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000007",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "81e4a552-0000-4000-8000-000000000002",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                            "pack_id": null,
                            "label": "Control de ortodoncia",
                            "quantity": 4,
                            "unit_price_clp": 28000,
                            "discount_clp": 0,
                            "total_clp": 112000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 28000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 1,
                            "note": null,
                            "sort_order": 0,
                            "accepted_at": "2026-09-23T01:07:07.912Z",
                            "carried_from_item_id": null,
                            "created_at": "2026-09-23T01:07:07.701Z",
                            "updated_at": "2026-09-23T01:07:07.911Z",
                            "service_name": "Control de ortodoncia"
                          },
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000008",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "81e4a552-0000-4000-8000-000000000002",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                            "pack_id": null,
                            "label": "Limpieza dental",
                            "quantity": 1,
                            "unit_price_clp": 35000,
                            "discount_clp": 5000,
                            "total_clp": 30000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 35000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 2,
                            "note": null,
                            "sort_order": 1,
                            "accepted_at": "2026-09-23T01:07:07.912Z",
                            "carried_from_item_id": null,
                            "created_at": "2026-09-23T01:07:07.718Z",
                            "updated_at": "2026-09-23T01:07:07.911Z",
                            "service_name": "Limpieza dental"
                          }
                        ],
                        "is_live": false,
                        "days_left": 100,
                        "version": 1,
                        "lineage_size": 2,
                        "reissued_by_id": "458363ad-0000-4000-8000-000000000001"
                      },
                      {
                        "id": "458363ad-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "display_seq": 3,
                        "display_id": "E-3",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                        "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                        "reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
                        "conversation_id": null,
                        "professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "price_list_id": null,
                        "agreement_id": null,
                        "legal_entity_id": null,
                        "status": "draft",
                        "subtotal_clp": 147000,
                        "discount_clp": 5000,
                        "total_clp": 142000,
                        "tax_treatment": "afecto",
                        "valid_until": "2026-12-31",
                        "installments": 3,
                        "notes": "Incluye los cuatro controles del primer semestre",
                        "obligation_group_id": null,
                        "rendered_document_id": null,
                        "created_by_user_id": null,
                        "sent_at": null,
                        "accepted_at": null,
                        "accepted_via": null,
                        "rejected_at": null,
                        "rejected_reason": null,
                        "expired_at": null,
                        "voided_at": null,
                        "voided_by": null,
                        "voided_reason": null,
                        "first_viewed_at": null,
                        "last_viewed_at": null,
                        "created_at": "2026-09-23T01:07:07.990Z",
                        "updated_at": "2026-09-23T01:07:07.990Z",
                        "patient_name": "María José Fuentes Lagos",
                        "professional_name": "Ana Rojas Vidal",
                        "items": [
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000009",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "458363ad-0000-4000-8000-000000000001",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                            "pack_id": null,
                            "label": "Control de ortodoncia",
                            "quantity": 4,
                            "unit_price_clp": 28000,
                            "discount_clp": 0,
                            "total_clp": 112000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 28000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 1,
                            "note": null,
                            "sort_order": 0,
                            "accepted_at": "2026-09-23T01:07:07.912Z",
                            "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
                            "created_at": "2026-09-23T01:07:07.990Z",
                            "updated_at": "2026-09-23T01:07:07.990Z",
                            "service_name": "Control de ortodoncia"
                          },
                          {
                            "id": "3a7d9767-0000-4000-8000-00000000000a",
                            "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                            "budget_id": "458363ad-0000-4000-8000-000000000001",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                            "pack_id": null,
                            "label": "Limpieza dental",
                            "quantity": 1,
                            "unit_price_clp": 35000,
                            "discount_clp": 5000,
                            "total_clp": 30000,
                            "price_explain": [
                              {
                                "kind": null,
                                "label": "Arancel particular 2026",
                                "order": 1,
                                "value": null,
                                "source": "price_list",
                                "rule_id": null,
                                "modifier": null,
                                "delta_clp": 0,
                                "amount_after_clp": 35000,
                                "amount_before_clp": null
                              }
                            ],
                            "tax_treatment": "afecto",
                            "phase": 2,
                            "note": null,
                            "sort_order": 1,
                            "accepted_at": "2026-09-23T01:07:07.912Z",
                            "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
                            "created_at": "2026-09-23T01:07:07.990Z",
                            "updated_at": "2026-09-23T01:07:07.990Z",
                            "service_name": "Limpieza dental"
                          }
                        ],
                        "is_live": true,
                        "days_left": 100,
                        "version": 2,
                        "lineage_size": 2,
                        "reissued_by_id": null
                      }
                    ],
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/render": {
      "get": {
        "summary": "The presupuesto as a PDF",
        "description": "Renders on demand and returns `application/pdf`. It is immutable BECAUSE every figure on it is frozen in the budget and its lines (ADR 0057), not because the bytes were stored.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "pattern": "^E-\\d+$"
                }
              ]
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The rendered presupuesto",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/items": {
      "post": {
        "summary": "Add a line, freezing its price",
        "description": "Resolves the price through the quote explainer and stores the ordered steps beside the number. An explicit `unit_price_clp` is recorded AS an override step, so \"why does it say $38.000\" stays answerable.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_service_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "pack_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "unit_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 9999999999
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999,
                    "default": 1
                  },
                  "discount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9999999999,
                    "default": 0
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado"
                    ]
                  },
                  "phase": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99,
                    "default": 1
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                "quantity": 1,
                "phase": 3,
                "note": "Control final"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Budget line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "3a7d9767-0000-4000-8000-000000000006",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "budget_id": "81e4a552-0000-4000-8000-000000000001",
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                    "pack_id": null,
                    "label": "Limpieza dental",
                    "quantity": 1,
                    "unit_price_clp": 35000,
                    "discount_clp": 0,
                    "total_clp": 35000,
                    "price_explain": [
                      {
                        "kind": null,
                        "label": "Arancel particular 2026",
                        "order": 1,
                        "value": null,
                        "source": "price_list",
                        "rule_id": null,
                        "modifier": null,
                        "delta_clp": 0,
                        "amount_after_clp": 35000,
                        "amount_before_clp": null
                      }
                    ],
                    "tax_treatment": "afecto",
                    "phase": 3,
                    "note": "Control final",
                    "sort_order": 2,
                    "created_at": "2026-09-22T22:21:15.470Z",
                    "updated_at": "2026-09-22T22:21:15.470Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budget-items/{id}": {
      "delete": {
        "summary": "Remove a line from a DRAFT",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Remove a line from a DRAFT\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Edit a line of a DRAFT",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999
                  },
                  "unit_price_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9999999999
                  },
                  "discount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 9999999999
                  },
                  "tax_treatment": {
                    "type": "string",
                    "enum": [
                      "afecto",
                      "exento",
                      "no_gravado"
                    ]
                  },
                  "phase": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "sort_order": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 999
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "quantity": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Budget line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "3a7d9767-0000-4000-8000-000000000006",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "budget_id": "81e4a552-0000-4000-8000-000000000001",
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                    "pack_id": null,
                    "label": "Limpieza dental",
                    "quantity": 2,
                    "unit_price_clp": 35000,
                    "discount_clp": 0,
                    "total_clp": 70000,
                    "price_explain": [
                      {
                        "kind": null,
                        "label": "Arancel particular 2026",
                        "order": 1,
                        "value": null,
                        "source": "price_list",
                        "rule_id": null,
                        "modifier": null,
                        "delta_clp": 0,
                        "amount_after_clp": 35000,
                        "amount_before_clp": null
                      }
                    ],
                    "tax_treatment": "afecto",
                    "phase": 3,
                    "note": "Control final",
                    "sort_order": 2,
                    "created_at": "2026-09-22T22:21:15.470Z",
                    "updated_at": "2026-09-22T22:21:15.481Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Edit a line of a DRAFT\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/budgets/{id}/send": {
      "post": {
        "summary": "Send it to the patient",
        "description": "Moves `draft → sent` FIRST (the freeze must be in force before the patient can read the figures), then delivers through the conversation with a tokenised accept link: WhatsApp while the window is open, email otherwise. A patient with neither is reported honestly (`delivered: \"none\"`) and the quote still counts as sent — the clinic handed it over. Also puts the lead on «Presupuesto enviado» with the total as its value.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "resend": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Send result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "budget": {
                      "id": "81e4a552-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "display_seq": 2,
                      "display_id": "E-2",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                      "reissued_from_id": null,
                      "conversation_id": null,
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "price_list_id": null,
                      "agreement_id": null,
                      "legal_entity_id": null,
                      "status": "sent",
                      "subtotal_clp": 147000,
                      "discount_clp": 5000,
                      "total_clp": 142000,
                      "tax_treatment": "afecto",
                      "valid_until": "2026-12-31",
                      "installments": 3,
                      "notes": "Incluye los cuatro controles del primer semestre",
                      "obligation_group_id": null,
                      "rendered_document_id": null,
                      "created_by_user_id": null,
                      "sent_at": "2026-09-22T22:21:15.544Z",
                      "accepted_at": null,
                      "accepted_via": null,
                      "rejected_at": null,
                      "rejected_reason": null,
                      "expired_at": null,
                      "voided_at": null,
                      "voided_by": null,
                      "voided_reason": null,
                      "created_at": "2026-09-22T22:21:15.502Z",
                      "updated_at": "2026-09-22T22:21:16.026Z",
                      "patient_name": "María José Fuentes Lagos",
                      "professional_name": "Ana Rojas Vidal",
                      "items": [
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000007",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                          "pack_id": null,
                          "label": "Control de ortodoncia",
                          "quantity": 4,
                          "unit_price_clp": 28000,
                          "discount_clp": 0,
                          "total_clp": 112000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 28000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 1,
                          "note": null,
                          "sort_order": 0,
                          "created_at": "2026-09-22T22:21:15.515Z",
                          "updated_at": "2026-09-22T22:21:15.515Z",
                          "service_name": "Control de ortodoncia"
                        },
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000008",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                          "pack_id": null,
                          "label": "Limpieza dental",
                          "quantity": 1,
                          "unit_price_clp": 35000,
                          "discount_clp": 5000,
                          "total_clp": 30000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 35000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 2,
                          "note": null,
                          "sort_order": 1,
                          "created_at": "2026-09-22T22:21:15.530Z",
                          "updated_at": "2026-09-22T22:21:15.530Z",
                          "service_name": "Limpieza dental"
                        }
                      ],
                      "is_live": true,
                      "days_left": 100
                    },
                    "delivered": "email",
                    "accept_url": "https://api.example.com/api/v1/public/clinic/budget/<token>",
                    "reason": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/accept": {
      "post": {
        "summary": "Accept it (STAFF path)",
        "description": "Stamps `accepted_via: \"staff\"` — the route decides, never the body. Accepts every line still presented, or (#2448) only `item_ids`: `status: \"accepted\"` means AT LEAST ONE line accepted, and the lines not yet accepted stay presented — the patient link keeps working for them, staff can accept more later, and `valid_until` still applies to them. Opens an `obligation_group` of kind `presupuesto` (one per lineage) plus one obligation per NEWLY accepted line in the payments ledger, creates or grows the treatment plan, and moves the lead to «Aceptado». Idempotent: a call with nothing left to accept returns the same objects with `accepted_now: false`; `accepted_item_ids` names the lines this call accepted. A presupuesto imported from Dentalink is never accepted: 409 `imported_balance_pending` while it carries a balance (collected via `/clinic/imported-balances`), else `imported_budget_readonly` (write a new native presupuesto to continue the treatment). An AGENT can reach neither this route nor the public one.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "item_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  }
                },
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accept result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "budget": {
                      "id": "81e4a552-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "display_seq": 2,
                      "display_id": "E-2",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                      "reissued_from_id": null,
                      "conversation_id": null,
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "price_list_id": null,
                      "agreement_id": null,
                      "legal_entity_id": null,
                      "status": "accepted",
                      "subtotal_clp": 147000,
                      "discount_clp": 5000,
                      "total_clp": 142000,
                      "tax_treatment": "afecto",
                      "valid_until": "2026-12-31",
                      "installments": 3,
                      "notes": "Incluye los cuatro controles del primer semestre",
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
                      "rendered_document_id": null,
                      "created_by_user_id": null,
                      "sent_at": "2026-09-22T22:21:15.544Z",
                      "accepted_at": "2026-09-22T22:21:16.040Z",
                      "accepted_via": "staff",
                      "rejected_at": null,
                      "rejected_reason": null,
                      "expired_at": null,
                      "voided_at": null,
                      "voided_by": null,
                      "voided_reason": null,
                      "created_at": "2026-09-22T22:21:15.502Z",
                      "updated_at": "2026-09-22T22:21:16.048Z",
                      "patient_name": "María José Fuentes Lagos",
                      "professional_name": "Ana Rojas Vidal",
                      "items": [
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000007",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                          "pack_id": null,
                          "label": "Control de ortodoncia",
                          "quantity": 4,
                          "unit_price_clp": 28000,
                          "discount_clp": 0,
                          "total_clp": 112000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 28000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 1,
                          "note": null,
                          "sort_order": 0,
                          "created_at": "2026-09-22T22:21:15.515Z",
                          "updated_at": "2026-09-22T22:21:15.515Z",
                          "service_name": "Control de ortodoncia"
                        },
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000008",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                          "pack_id": null,
                          "label": "Limpieza dental",
                          "quantity": 1,
                          "unit_price_clp": 35000,
                          "discount_clp": 5000,
                          "total_clp": 30000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 35000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 2,
                          "note": null,
                          "sort_order": 1,
                          "created_at": "2026-09-22T22:21:15.530Z",
                          "updated_at": "2026-09-22T22:21:15.530Z",
                          "service_name": "Limpieza dental"
                        }
                      ],
                      "is_live": false,
                      "days_left": 100
                    },
                    "plan": {
                      "id": "1ccffee2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "name": "Ortodoncia fija superior e inferior",
                      "diagnosis": "Apiñamiento anterosuperior moderado",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "status": "proposed",
                      "accepted_at": null,
                      "completed_at": null,
                      "abandoned_reason": null,
                      "created_at": "2026-09-22T22:21:15.213Z",
                      "updated_at": "2026-09-22T22:21:15.285Z",
                      "patient_name": "María José Fuentes Lagos",
                      "professional_name": "Ana Rojas Vidal",
                      "items": [
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000002",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                          "phase": 1,
                          "quantity": 4,
                          "tooth_or_zone": null,
                          "status": "scheduled",
                          "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                          "budget_item_id": null,
                          "sort_order": 0,
                          "created_at": "2026-09-22T22:21:15.215Z",
                          "updated_at": "2026-09-22T22:21:15.337Z",
                          "service_name": "Control de ortodoncia"
                        },
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000003",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                          "phase": 2,
                          "quantity": 1,
                          "tooth_or_zone": null,
                          "status": "pending",
                          "appointment_id": null,
                          "budget_item_id": null,
                          "sort_order": 1,
                          "created_at": "2026-09-22T22:21:15.215Z",
                          "updated_at": "2026-09-22T22:21:15.215Z",
                          "service_name": "Limpieza dental"
                        }
                      ],
                      "progress": {
                        "pending": 1,
                        "scheduled": 4,
                        "performed": 0,
                        "cancelled": 0,
                        "active": 5,
                        "percent": 0,
                        "phases": [
                          {
                            "phase": 1,
                            "pending": 0,
                            "scheduled": 4,
                            "performed": 0,
                            "cancelled": 0,
                            "active": 4,
                            "percent": 0
                          },
                          {
                            "phase": 2,
                            "pending": 1,
                            "scheduled": 0,
                            "performed": 0,
                            "cancelled": 0,
                            "active": 1,
                            "percent": 0
                          }
                        ],
                        "current_phase": 1
                      }
                    },
                    "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
                    "obligation_ids": [
                      "b44936d0-0000-4000-8000-000000000002",
                      "b44936d0-0000-4000-8000-000000000003"
                    ],
                    "accepted_now": true,
                    "accepted_item_ids": [
                      "3a7d9767-0000-4000-8000-000000000007",
                      "3a7d9767-0000-4000-8000-000000000008"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/accept-and-collect": {
      "post": {
        "summary": "Collect a payment and accept the lines it pays (the counter)",
        "description": "Reception's one gesture (#2448): the lines in `item_ids` become accepted (`accepted_via: \"staff\"`, stamped with the cashier), their obligations open in the presupuesto group, and the payment is recorded and allocated to exactly those obligations — in ONE transaction, so a refused payment or allocation leaves the lines presented. The payer is the budget's `contact_id` (409 `payer_unknown` without one) and the caja its `location_id`. Money beyond the lines' total stays as the patient's credit; less leaves the last line partly paid. A POS swipe is a `tarjeta*` instrument with `source: \"card_terminal\"`. Requires BOTH `clinic_money:write` and `payments:write`. 409 `nothing_to_accept` when the lines are already accepted, `expired` / `not_sent` like accept, and `imported_balance_pending` / `imported_budget_readonly` for a presupuesto imported from Dentalink (never accepted here). `Idempotency-Key` is stored on the payment. An AGENT cannot reach it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "item_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "payment": {
                    "type": "object",
                    "properties": {
                      "instrument": {
                        "type": "string",
                        "enum": [
                          "efectivo",
                          "transferencia",
                          "cheque",
                          "vale_vista",
                          "tarjeta",
                          "tarjeta_debito",
                          "tarjeta_credito",
                          "bono",
                          "otro",
                          "mercadopago",
                          "webpay",
                          "fintoc",
                          "flow",
                          "khipu",
                          "bancame"
                        ]
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "manual",
                          "card_terminal"
                        ]
                      },
                      "amount_clp": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 999999999999
                      },
                      "paid_on": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "bank": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "account_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "document_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "note": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 2000
                      },
                      "card_fee_bps": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 10000
                      }
                    },
                    "required": [
                      "instrument",
                      "amount_clp"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "item_ids",
                  "payment"
                ],
                "additionalProperties": false
              },
              "example": {
                "item_ids": [
                  "3a7d9767-0000-4000-8000-000000000007"
                ],
                "payment": {
                  "instrument": "tarjeta_debito",
                  "source": "card_terminal",
                  "amount_clp": 112000
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Accept-and-collect result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "budget": {
                      "id": "81e4a552-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "display_seq": 2,
                      "display_id": "E-2",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                      "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                      "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                      "reissued_from_id": null,
                      "conversation_id": null,
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "price_list_id": null,
                      "agreement_id": null,
                      "legal_entity_id": null,
                      "status": "accepted",
                      "subtotal_clp": 147000,
                      "discount_clp": 5000,
                      "total_clp": 142000,
                      "tax_treatment": "afecto",
                      "valid_until": "2026-12-31",
                      "installments": 3,
                      "notes": "Incluye los cuatro controles del primer semestre",
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
                      "rendered_document_id": null,
                      "created_by_user_id": null,
                      "sent_at": "2026-09-22T22:21:15.544Z",
                      "accepted_at": "2026-09-22T22:21:16.040Z",
                      "accepted_via": "staff",
                      "rejected_at": null,
                      "rejected_reason": null,
                      "expired_at": null,
                      "voided_at": null,
                      "voided_by": null,
                      "voided_reason": null,
                      "created_at": "2026-09-22T22:21:15.502Z",
                      "updated_at": "2026-09-22T22:21:16.048Z",
                      "patient_name": "María José Fuentes Lagos",
                      "professional_name": "Ana Rojas Vidal",
                      "items": [
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000007",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                          "pack_id": null,
                          "label": "Control de ortodoncia",
                          "quantity": 4,
                          "unit_price_clp": 28000,
                          "discount_clp": 0,
                          "total_clp": 112000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 28000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 1,
                          "note": null,
                          "sort_order": 0,
                          "created_at": "2026-09-22T22:21:15.515Z",
                          "updated_at": "2026-09-22T22:21:15.515Z",
                          "service_name": "Control de ortodoncia"
                        },
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000008",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "budget_id": "81e4a552-0000-4000-8000-000000000002",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                          "pack_id": null,
                          "label": "Limpieza dental",
                          "quantity": 1,
                          "unit_price_clp": 35000,
                          "discount_clp": 5000,
                          "total_clp": 30000,
                          "price_explain": [
                            {
                              "kind": null,
                              "label": "Arancel particular 2026",
                              "order": 1,
                              "value": null,
                              "source": "price_list",
                              "rule_id": null,
                              "modifier": null,
                              "delta_clp": 0,
                              "amount_after_clp": 35000,
                              "amount_before_clp": null
                            }
                          ],
                          "tax_treatment": "afecto",
                          "phase": 2,
                          "note": null,
                          "sort_order": 1,
                          "created_at": "2026-09-22T22:21:15.530Z",
                          "updated_at": "2026-09-22T22:21:15.530Z",
                          "service_name": "Limpieza dental"
                        }
                      ],
                      "is_live": false,
                      "days_left": 100
                    },
                    "plan": {
                      "id": "1ccffee2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "name": "Ortodoncia fija superior e inferior",
                      "diagnosis": "Apiñamiento anterosuperior moderado",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "status": "proposed",
                      "accepted_at": null,
                      "completed_at": null,
                      "abandoned_reason": null,
                      "created_at": "2026-09-22T22:21:15.213Z",
                      "updated_at": "2026-09-22T22:21:15.285Z",
                      "patient_name": "María José Fuentes Lagos",
                      "professional_name": "Ana Rojas Vidal",
                      "items": [
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000002",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                          "phase": 1,
                          "quantity": 4,
                          "tooth_or_zone": null,
                          "status": "scheduled",
                          "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                          "budget_item_id": null,
                          "sort_order": 0,
                          "created_at": "2026-09-22T22:21:15.215Z",
                          "updated_at": "2026-09-22T22:21:15.337Z",
                          "service_name": "Control de ortodoncia"
                        },
                        {
                          "id": "3a7d9767-0000-4000-8000-000000000003",
                          "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                          "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                          "phase": 2,
                          "quantity": 1,
                          "tooth_or_zone": null,
                          "status": "pending",
                          "appointment_id": null,
                          "budget_item_id": null,
                          "sort_order": 1,
                          "created_at": "2026-09-22T22:21:15.215Z",
                          "updated_at": "2026-09-22T22:21:15.215Z",
                          "service_name": "Limpieza dental"
                        }
                      ],
                      "progress": {
                        "pending": 1,
                        "scheduled": 4,
                        "performed": 0,
                        "cancelled": 0,
                        "active": 5,
                        "percent": 0,
                        "phases": [
                          {
                            "phase": 1,
                            "pending": 0,
                            "scheduled": 4,
                            "performed": 0,
                            "cancelled": 0,
                            "active": 4,
                            "percent": 0
                          },
                          {
                            "phase": 2,
                            "pending": 1,
                            "scheduled": 0,
                            "performed": 0,
                            "cancelled": 0,
                            "active": 1,
                            "percent": 0
                          }
                        ],
                        "current_phase": 1
                      }
                    },
                    "obligation_group_id": "a067dce6-0000-4000-8000-000000000002",
                    "obligation_ids": [
                      "b44936d0-0000-4000-8000-000000000002"
                    ],
                    "accepted_now": true,
                    "accepted_item_ids": [
                      "3a7d9767-0000-4000-8000-000000000007"
                    ],
                    "payment": {
                      "payment": {
                        "id": "9a7e0000-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "source": "card_terminal",
                        "provenance": "manual",
                        "instrument": "tarjeta_debito",
                        "amount_clp": 112000,
                        "paid_on": "2026-09-22",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "allocated_clp": 112000,
                        "unallocated_clp": 0
                      },
                      "applied": [
                        {
                          "obligationId": "b44936d0-0000-4000-8000-000000000002",
                          "amountClp": 112000,
                          "allocatedClp": 112000,
                          "outstandingClp": 0,
                          "expectedClp": 112000,
                          "state": "paid",
                          "label": "Control de ortodoncia"
                        }
                      ],
                      "surplus_clp": 0,
                      "reasons": [
                        "payer_recorded_by_staff",
                        "allocation_staff_manual"
                      ],
                      "replayed": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/reject": {
      "post": {
        "summary": "Record the patient declining",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason": "El paciente prefiere esperar al próximo semestre"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "90afb8c1-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 3,
                    "display_id": "E-3",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                    "lineage_id": "90afb8c1-0000-4000-8000-000000000001",
                    "reissued_from_id": null,
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "rejected",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles del primer semestre",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": "2026-09-22T22:21:16.137Z",
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": "2026-09-22T22:21:16.460Z",
                    "rejected_reason": "El paciente prefiere esperar al próximo semestre",
                    "expired_at": null,
                    "voided_at": null,
                    "voided_by": null,
                    "voided_reason": null,
                    "created_at": "2026-09-22T22:21:16.091Z",
                    "updated_at": "2026-09-22T22:21:16.459Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000009",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "90afb8c1-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:16.106Z",
                        "updated_at": "2026-09-22T22:21:16.106Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-00000000000a",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "90afb8c1-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:16.121Z",
                        "updated_at": "2026-09-22T22:21:16.121Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": false,
                    "days_left": 100
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Record the patient declining\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/budgets/{id}/supersede": {
      "post": {
        "summary": "Write a new version (Nueva versión)",
        "description": "From `sent`, `accepted`, `rejected` or `expired` (409 otherwise, and 409 when a version already supersedes it). Creates a DRAFT copying the header and every line verbatim, in the same lineage. Lines already ACCEPTED are carried forward (`carried_from_item_id`, their original `accepted_at`), are immutable on the draft (409 `carried_item`), and open no second obligation when the new version is accepted — which reuses the predecessor's obligation group. Nothing is withdrawn until the draft is SENT: that voids a sent, rejected or expired predecessor with reason `superseded` in the same transaction. An ACCEPTED predecessor stays accepted until the new version is itself accepted, so its debt always has an accepted document. Discarding the draft leaves the predecessor untouched. Same rules as `POST /clinic/budgets` with `reissued_from_id`. A presupuesto photographed from a mirrored Dentalink plan (`mirror` set) is RE-PHOTOGRAPHED instead: accepted lines are still carried, every other line comes from the plan's live state, and the new version records that state — born without `mirror.changed_at` (409 `MIRROR_LINES_UNMAPPED` if the live plan has a prestación missing from the catalogue).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "458363ad-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 3,
                    "display_id": "E-3",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                    "lineage_id": "81e4a552-0000-4000-8000-000000000002",
                    "reissued_from_id": "81e4a552-0000-4000-8000-000000000002",
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "draft",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles del primer semestre",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": null,
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": null,
                    "rejected_reason": null,
                    "expired_at": null,
                    "voided_at": null,
                    "voided_by": null,
                    "voided_reason": null,
                    "first_viewed_at": null,
                    "last_viewed_at": null,
                    "created_at": "2026-09-23T01:07:07.990Z",
                    "updated_at": "2026-09-23T01:07:07.990Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000009",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "458363ad-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "accepted_at": "2026-09-23T01:07:07.912Z",
                        "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000007",
                        "created_at": "2026-09-23T01:07:07.990Z",
                        "updated_at": "2026-09-23T01:07:07.990Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-00000000000a",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "458363ad-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "accepted_at": "2026-09-23T01:07:07.912Z",
                        "carried_from_item_id": "3a7d9767-0000-4000-8000-000000000008",
                        "created_at": "2026-09-23T01:07:07.990Z",
                        "updated_at": "2026-09-23T01:07:07.990Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": true,
                    "days_left": 100,
                    "version": 2,
                    "lineage_size": 2,
                    "reissued_by_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/budgets/{id}/void": {
      "post": {
        "summary": "Void it — never DELETE",
        "description": "Keeps the folio and stays listable (ADR 0058 §10). `reason` is required by the service AND by a database CHECK: \"why did we withdraw 40 quotes this month\" is only answerable if it was written down. An ACCEPTED presupuesto cannot be voided here, and the reason `superseded` is reserved — write a new version instead (`POST /clinic/budgets/{id}/supersede`).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Se emitió con el arancel equivocado"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic budget",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e9cede9b-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "display_seq": 4,
                    "display_id": "E-4",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                    "lead_id": "8cb33b27-0000-4000-8000-000000000001",
                    "lineage_id": "e9cede9b-0000-4000-8000-000000000001",
                    "reissued_from_id": null,
                    "conversation_id": null,
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "price_list_id": null,
                    "agreement_id": null,
                    "legal_entity_id": null,
                    "status": "voided",
                    "subtotal_clp": 147000,
                    "discount_clp": 5000,
                    "total_clp": 142000,
                    "tax_treatment": "afecto",
                    "valid_until": "2026-12-31",
                    "installments": 3,
                    "notes": "Incluye los cuatro controles del primer semestre",
                    "obligation_group_id": null,
                    "rendered_document_id": null,
                    "created_by_user_id": null,
                    "sent_at": "2026-09-22T22:21:16.514Z",
                    "accepted_at": null,
                    "accepted_via": null,
                    "rejected_at": null,
                    "rejected_reason": null,
                    "expired_at": null,
                    "voided_at": "2026-09-22T22:21:16.806Z",
                    "voided_by": null,
                    "voided_reason": "Se emitió con el arancel equivocado",
                    "created_at": "2026-09-22T22:21:16.473Z",
                    "updated_at": "2026-09-22T22:21:16.805Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-00000000000b",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "e9cede9b-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "pack_id": null,
                        "label": "Control de ortodoncia",
                        "quantity": 4,
                        "unit_price_clp": 28000,
                        "discount_clp": 0,
                        "total_clp": 112000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 28000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 1,
                        "note": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:16.485Z",
                        "updated_at": "2026-09-22T22:21:16.485Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-00000000000c",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "budget_id": "e9cede9b-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "pack_id": null,
                        "label": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 5000,
                        "total_clp": 30000,
                        "price_explain": [
                          {
                            "kind": null,
                            "label": "Arancel particular 2026",
                            "order": 1,
                            "value": null,
                            "source": "price_list",
                            "rule_id": null,
                            "modifier": null,
                            "delta_clp": 0,
                            "amount_after_clp": 35000,
                            "amount_before_clp": null
                          }
                        ],
                        "tax_treatment": "afecto",
                        "phase": 2,
                        "note": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:16.500Z",
                        "updated_at": "2026-09-22T22:21:16.500Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "is_live": false,
                    "days_left": 100
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/treatment-plans": {
      "get": {
        "summary": "List treatment plans, with phase progress",
        "description": "Progress is DERIVED from the items (pending / scheduled / performed), never a stored counter — a counter drifts the first time a cita is cancelled. `?live=true` is accepted + in_progress, the work still to do.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "proposed",
                "accepted",
                "in_progress",
                "completed",
                "abandoned"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "live",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Treatment plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [],
                    "total": 0,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a treatment plan by hand",
        "description": "Accepting a presupuesto creates one automatically; this is the path for a plan that never had a quote. Clinical authoring: accepts `clinic_money:write` or `clinic_record:write` (items carry no price); a clinical-only caller must be able to open the patient's ficha.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "diagnosis": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "phase": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 99,
                          "default": 1
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999,
                          "default": 1
                        },
                        "tooth_or_zone": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "clinic_service_id"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "clinic_patient_id",
                  "name"
                ],
                "additionalProperties": false
              },
              "example": {
                "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "name": "Ortodoncia fija superior e inferior",
                "diagnosis": "Apiñamiento anterosuperior moderado",
                "professional_id": "18342d1b-0000-4000-8000-000000000001",
                "items": [
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                    "phase": 1,
                    "quantity": 4
                  },
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                    "phase": 2,
                    "quantity": 1
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Treatment plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "1ccffee2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "name": "Ortodoncia fija superior e inferior",
                    "diagnosis": "Apiñamiento anterosuperior moderado",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "status": "draft",
                    "accepted_at": null,
                    "completed_at": null,
                    "abandoned_reason": null,
                    "created_at": "2026-09-22T22:21:15.213Z",
                    "updated_at": "2026-09-22T22:21:15.213Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "phase": 1,
                        "quantity": 4,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "phase": 2,
                        "quantity": 1,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "progress": {
                      "pending": 5,
                      "scheduled": 0,
                      "performed": 0,
                      "cancelled": 0,
                      "active": 5,
                      "percent": 0,
                      "phases": [
                        {
                          "phase": 1,
                          "pending": 4,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 4,
                          "percent": 0
                        },
                        {
                          "phase": 2,
                          "pending": 1,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 1,
                          "percent": 0
                        }
                      ],
                      "current_phase": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/treatment-plans/{id}": {
      "get": {
        "summary": "One plan with its phases and progress",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Treatment plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "1ccffee2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "name": "Ortodoncia fija superior e inferior",
                    "diagnosis": "Apiñamiento anterosuperior moderado",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "status": "draft",
                    "accepted_at": null,
                    "completed_at": null,
                    "abandoned_reason": null,
                    "created_at": "2026-09-22T22:21:15.213Z",
                    "updated_at": "2026-09-22T22:21:15.213Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "phase": 1,
                        "quantity": 4,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "phase": 2,
                        "quantity": 1,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "progress": {
                      "pending": 5,
                      "scheduled": 0,
                      "performed": 0,
                      "cancelled": 0,
                      "active": 5,
                      "percent": 0,
                      "phases": [
                        {
                          "phase": 1,
                          "pending": 4,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 4,
                          "percent": 0
                        },
                        {
                          "phase": 2,
                          "pending": 1,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 1,
                          "percent": 0
                        }
                      ],
                      "current_phase": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "One plan with its phases and progress\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "delete": {
        "summary": "Delete a plan",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Delete a plan\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Edit a plan",
        "description": "`status: \"abandoned\"` requires `abandoned_reason` — \"why do plans die at phase 2\" is only answerable if somebody wrote it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "diagnosis": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "proposed",
                      "accepted",
                      "in_progress",
                      "completed",
                      "abandoned"
                    ]
                  },
                  "abandoned_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "status": "proposed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Treatment plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "1ccffee2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "name": "Ortodoncia fija superior e inferior",
                    "diagnosis": "Apiñamiento anterosuperior moderado",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "status": "proposed",
                    "accepted_at": null,
                    "completed_at": null,
                    "abandoned_reason": null,
                    "created_at": "2026-09-22T22:21:15.213Z",
                    "updated_at": "2026-09-22T22:21:15.285Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "phase": 1,
                        "quantity": 4,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "phase": 2,
                        "quantity": 1,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "progress": {
                      "pending": 5,
                      "scheduled": 0,
                      "performed": 0,
                      "cancelled": 0,
                      "active": 5,
                      "percent": 0,
                      "phases": [
                        {
                          "phase": 1,
                          "pending": 4,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 4,
                          "percent": 0
                        },
                        {
                          "phase": 2,
                          "pending": 1,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 1,
                          "percent": 0
                        }
                      ],
                      "current_phase": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/treatment-plans/{id}/items": {
      "post": {
        "summary": "Add prestaciones to an open plan",
        "description": "Clinical authoring (an odontograma finding, a new phase): accepts `clinic_money:write` or `clinic_record:write`. Items carry no price — a presupuesto prices them. A clinical-only caller must be able to open the patient's ficha (own patients). A completed or abandoned plan answers 409 `clinic_plan_closed`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "phase": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 99
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999,
                          "default": 1
                        },
                        "tooth_or_zone": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "clinic_service_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 100
                  }
                },
                "required": [
                  "items"
                ],
                "additionalProperties": false
              },
              "example": {
                "items": [
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                    "quantity": 1,
                    "tooth_or_zone": {
                      "fdi": [
                        16
                      ]
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Treatment plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "1ccffee2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "name": "Ortodoncia fija superior e inferior",
                    "diagnosis": "Apiñamiento anterosuperior moderado",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "status": "proposed",
                    "accepted_at": null,
                    "completed_at": null,
                    "abandoned_reason": null,
                    "created_at": "2026-09-22T22:21:15.213Z",
                    "updated_at": "2026-09-22T22:21:15.285Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "phase": 1,
                        "quantity": 4,
                        "tooth_or_zone": null,
                        "status": "scheduled",
                        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                        "budget_item_id": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.337Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "phase": 2,
                        "quantity": 1,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "progress": {
                      "pending": 1,
                      "scheduled": 4,
                      "performed": 0,
                      "cancelled": 0,
                      "active": 5,
                      "percent": 0,
                      "phases": [
                        {
                          "phase": 1,
                          "pending": 0,
                          "scheduled": 4,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 4,
                          "percent": 0
                        },
                        {
                          "phase": 2,
                          "pending": 1,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 1,
                          "percent": 0
                        }
                      ],
                      "current_phase": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/treatment-plan-items/{id}/status": {
      "post": {
        "summary": "Move one phase item along",
        "description": "`pending → scheduled → performed` (or `cancelled`). `scheduled` requires the cita it lands on. The PLAN's own status follows from its items, so nobody has to remember to close a plan whose last phase was performed.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending",
                      "scheduled",
                      "performed",
                      "cancelled"
                    ]
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false
              },
              "example": {
                "status": "scheduled",
                "appointment_id": "d93e8eec-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Treatment plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "1ccffee2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "name": "Ortodoncia fija superior e inferior",
                    "diagnosis": "Apiñamiento anterosuperior moderado",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "status": "proposed",
                    "accepted_at": null,
                    "completed_at": null,
                    "abandoned_reason": null,
                    "created_at": "2026-09-22T22:21:15.213Z",
                    "updated_at": "2026-09-22T22:21:15.285Z",
                    "patient_name": "María José Fuentes Lagos",
                    "professional_name": "Ana Rojas Vidal",
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "phase": 1,
                        "quantity": 4,
                        "tooth_or_zone": null,
                        "status": "scheduled",
                        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                        "budget_item_id": null,
                        "sort_order": 0,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.337Z",
                        "service_name": "Control de ortodoncia"
                      },
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "treatment_plan_id": "1ccffee2-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000002",
                        "phase": 2,
                        "quantity": 1,
                        "tooth_or_zone": null,
                        "status": "pending",
                        "appointment_id": null,
                        "budget_item_id": null,
                        "sort_order": 1,
                        "created_at": "2026-09-22T22:21:15.215Z",
                        "updated_at": "2026-09-22T22:21:15.215Z",
                        "service_name": "Limpieza dental"
                      }
                    ],
                    "progress": {
                      "pending": 1,
                      "scheduled": 4,
                      "performed": 0,
                      "cancelled": 0,
                      "active": 5,
                      "percent": 0,
                      "phases": [
                        {
                          "phase": 1,
                          "pending": 0,
                          "scheduled": 4,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 4,
                          "percent": 0
                        },
                        {
                          "phase": 2,
                          "pending": 1,
                          "scheduled": 0,
                          "performed": 0,
                          "cancelled": 0,
                          "active": 1,
                          "percent": 0
                        }
                      ],
                      "current_phase": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/imported-balances": {
      "get": {
        "summary": "«Saldos pendientes de Dentalink»: what patients still owe on imported presupuestos",
        "description": "Read-only. One row per patient whose presupuestos imported from Dentalink by the instructed migration run (#2467) still carry a balance: the balance at cutover (`data.imported.deuda` of each imported, non-voided presupuesto), what was collected in Vitrina since, the pending `balance_clp`, the presupuestos it sits on (folio + the Dentalink plan id and date), and the last payment — `last_vendor_payment_on` from the revenue mirror (voided payments excluded), `last_collected_on` from collections in Vitrina, `last_payment_on` the later of the two (`YYYY-MM-DD`). A balance leaves the list when it is collected in full (`POST /clinic/imported-balances/{budget_id}/collect`), or when its presupuesto is voided; a partial collection lowers it. A collected payment that is later reversed stays owed in the native ledger, not back on this list. No charge, obligation, reminder, follow-up, commission or boleta is ever produced by a listed balance on its own. `q` matches a name (accent-folded), a RUT prefix (any format) or a phone fragment; `sort` is `balance` (default, largest first), `name` (by apellido) or `last_payment`. `summary` is the whole list, whatever `q` says. Requires `clinic_money:read` (and `clinic_patients:read` for an API key).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "balance",
                "name",
                "last_payment"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 100000
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicImportedBalanceList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rows": [
                      {
                        "patient": {
                          "id": "b1b0b8de-0000-4000-8000-000000000001",
                          "nombre": "María José",
                          "apellidos": "Fuentes Lagos",
                          "rut": "11.111.111-1",
                          "numero_ficha": "4412"
                        },
                        "balance_clp": 110000,
                        "cutover_balance_clp": 150000,
                        "collected_clp": 40000,
                        "last_vendor_payment_on": "2026-10-01",
                        "last_collected_on": "2026-11-03",
                        "last_payment_on": "2026-11-03",
                        "budgets": [
                          {
                            "budget_id": "81e4a552-0000-4000-8000-000000000011",
                            "display_id": "E-412",
                            "status": "accepted",
                            "external_id": "18233",
                            "plan_date": "2026-06-01",
                            "cutover_balance_clp": 100000,
                            "collected_clp": 40000,
                            "balance_clp": 60000
                          },
                          {
                            "budget_id": "81e4a552-0000-4000-8000-000000000012",
                            "display_id": "E-398",
                            "status": "expired",
                            "external_id": "17120",
                            "plan_date": "2026-03-01",
                            "cutover_balance_clp": 50000,
                            "collected_clp": 0,
                            "balance_clp": 50000
                          }
                        ]
                      }
                    ],
                    "total": 1,
                    "offset": 0,
                    "limit": 50,
                    "summary": {
                      "patients": 1,
                      "balance_clp": 110000,
                      "cutover_balance_clp": 150000,
                      "collected_clp": 40000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/imported-balances/{budget_id}": {
      "get": {
        "summary": "One imported presupuesto’s Dentalink balance and default payer",
        "description": "The pending balance of one presupuesto imported from Dentalink (0 when voided) and the payer a collection is recorded for when the request names none: the presupuesto’s own `contact_id`, else the patient’s reading bond or verified link, else a payer bond a collection left. `payer: null` means a collection must name one. `credits` lists the payer’s money not yet applied to anything (a transfer the bank-email matcher recorded, the surplus of an earlier payment) — the payer named by `contact_id`, else the default one; any of them can settle the balance with `credit_payment_id`. `will_bond_payer` (with `contact_id`) says whether collecting with that payer leaves a new payer bond. 409 `not_imported` for a presupuesto created in Vitrina. Requires `clinic_money:read` (and `clinic_patients:read` for an API key).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "budget_id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The balance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicImportedBalanceDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "budget_id": "81e4a552-0000-4000-8000-000000000011",
                    "display_id": "E-412",
                    "status": "accepted",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "cutover_balance_clp": 100000,
                    "balance_clp": 60000,
                    "payer": {
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "name": "María José Fuentes Lagos"
                    },
                    "will_bond_payer": false,
                    "credits": [
                      {
                        "payment_id": "c0ffee00-0000-4000-8000-000000000021",
                        "paid_on": "2026-11-02",
                        "instrument": "transferencia",
                        "amount_clp": 30000,
                        "unallocated_clp": 30000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/imported-balances/{budget_id}/collect": {
      "post": {
        "summary": "Collect a payment (or apply credit) against a Dentalink balance",
        "description": "Send exactly one of `payment` or `credit_payment_id`. With `payment`, records it through the payments ledger exactly like any counter payment (caja shift of the presupuesto’s sucursal, card recargo, `clinic.payment.registered`), and — in the same transaction — opens ONE obligation in the presupuesto’s balance group for the amount applied: `min(amount_clp, pending balance)`, no due date, allocated at once, so it is born settled. Money beyond the balance stays as the payer’s credit. With `credit_payment_id`, applies what that payment still has unallocated (its payer pays; `contact_id` is ignored) the same way, as one write; 409 `no_credit` when nothing is left on it. Two collections of one balance are serialised on the presupuesto; neither can take it below zero. `contact_id` names the payer; omitted, the default payer of `GET /clinic/imported-balances/{budget_id}` is used (409 `payer_unknown` when there is none). When the patient had no payer at all, the payer this collection names becomes a bond (`can_book`, never `can_read_record`, `verified_method: \"manual\"`, role `otro`; `payer_bond_id`) and is the default payer next time. 409 `not_imported`, `budget_voided`, `nothing_to_collect`. `Idempotency-Key` is stored on the payment: a repeat records and applies nothing and echoes the first payment (`payment.replayed: true` once the request cache has expired); a key already used for another gesture is 409 `idempotency_key_reused`. Requires BOTH `clinic_money:write` and `payments:write`. An AGENT cannot reach it.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "budget_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "payment": {
                    "type": "object",
                    "properties": {
                      "instrument": {
                        "type": "string",
                        "enum": [
                          "efectivo",
                          "transferencia",
                          "cheque",
                          "vale_vista",
                          "tarjeta",
                          "tarjeta_debito",
                          "tarjeta_credito",
                          "bono",
                          "otro",
                          "mercadopago",
                          "webpay",
                          "fintoc",
                          "flow",
                          "khipu",
                          "bancame"
                        ]
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "manual",
                          "card_terminal"
                        ]
                      },
                      "amount_clp": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 999999999999
                      },
                      "paid_on": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      },
                      "bank": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "account_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "document_number": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "note": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 2000
                      },
                      "card_fee_bps": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 0,
                        "maximum": 10000
                      }
                    },
                    "required": [
                      "instrument",
                      "amount_clp"
                    ],
                    "additionalProperties": false
                  },
                  "credit_payment_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "payment": {
                  "instrument": "tarjeta_debito",
                  "source": "card_terminal",
                  "amount_clp": 40000
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replayed (same Idempotency-Key)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicImportedBalanceCollectResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Collected",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicImportedBalanceCollectResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "budget_id": "81e4a552-0000-4000-8000-000000000011",
                    "display_id": "E-412",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "applied_clp": 40000,
                    "balance_before_clp": 100000,
                    "balance_after_clp": 60000,
                    "obligation_group_id": "a067dce6-0000-4000-8000-000000000011",
                    "obligation_id": "b44936d0-0000-4000-8000-000000000011",
                    "payer_bond_id": null,
                    "payment": {
                      "payment": {
                        "id": "c0ffee00-0000-4000-8000-000000000011",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "suggested_contact_id": null,
                        "payer_reasons": [
                          "payer_recorded_by_staff"
                        ],
                        "source": "card_terminal",
                        "provenance": "manual",
                        "instrument": "tarjeta_debito",
                        "amount_clp": 40000,
                        "paid_on": "2026-11-03",
                        "allocated_clp": 40000,
                        "unallocated_clp": 0,
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "recorded_at": "2026-11-03T13:11:26.575Z",
                        "reversed": false,
                        "allocations": [
                          {
                            "id": "a11ca7e0-0000-4000-8000-000000000011",
                            "customer_payment_id": "c0ffee00-0000-4000-8000-000000000011",
                            "customer_obligation_id": "b44936d0-0000-4000-8000-000000000011",
                            "amount_clp": 40000,
                            "method": "staff",
                            "reasons": [
                              "allocation_staff_manual"
                            ],
                            "allocated_at": "2026-11-03T13:11:26.575Z",
                            "released_at": null,
                            "live": true
                          }
                        ]
                      },
                      "applied": [
                        {
                          "obligationId": "b44936d0-0000-4000-8000-000000000011",
                          "amountClp": 40000,
                          "allocatedClp": 40000,
                          "outstandingClp": 0,
                          "expectedClp": 40000,
                          "state": "paid",
                          "label": "Saldo de tratamiento · E-412"
                        }
                      ],
                      "surplus_clp": 0,
                      "reasons": [
                        "payer_recorded_by_staff",
                        "allocation_staff_manual"
                      ],
                      "replayed": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/tools-catalog": {
      "get": {
        "summary": "Tools this workspace may wire to an agent",
        "description": "The catalogue is TENANT-RESOLVED, not static: tools gated on a vertical or an unconnected integration are filtered out, so what comes back is what this workspace can actually use today. Connecting an integration or changing the vertical changes this list.\n\nNames from here are what `PUT /ai-agents/{id}/tools` accepts.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "The gated tool catalogue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "platform": [
                      {
                        "key": "search_vehicle_stock",
                        "name": "search_vehicle_stock",
                        "description": "Search the dealer's current vehicle stock to answer availability/price questions.",
                        "kind": "platform",
                        "available": true
                      }
                    ],
                    "custom": [
                      {
                        "key": "consultar_clima",
                        "name": "consultar_clima",
                        "description": "Consulta el clima actual de una ciudad",
                        "kind": "custom",
                        "available": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/metrics": {
      "get": {
        "summary": "Rolling performance metrics for one agent",
        "description": "The detail screen’s header strip. A ROLLING window ending now: `days` back from the request, default 7, max 90.\n\n`conversations`, `resolved_without_human` and `handoffs` are the same numbers the Insights tab shows, read from the `insights_ai_agents` RPC — a conversation counts for an agent when that agent posted a REAL turn in it inside the window (template sends, tool-call rows and reasoning traces are not turns). `resolved_without_human` = no human wrote AND the agent never called `handoff`; `handoffs` = the agent called `handoff`, with `handoffs_answered` the subset a human then replied to. `pct` is a FRACTION of `conversations`, and is `null` — not 0 — when there were none.\n\n`first_response_seconds` is a MEDIAN, not a mean, over conversations STARTED in the window, pairing each one’s first inbound message with this agent’s first later reply; `null` when no conversation has both. `appointments_booked` counts completed booking-tool executions.\n\nAn agent with no traffic answers 200 with zeros and nulls, never 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 7
            },
            "required": false,
            "name": "days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent metrics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "days": 7,
                    "from": "2026-09-15T16:38:21.402Z",
                    "to": "2026-09-22T16:38:21.402Z",
                    "conversations": 18,
                    "resolved_without_human": {
                      "count": 4,
                      "pct": 0.22
                    },
                    "handoffs": {
                      "count": 2,
                      "pct": 0.11
                    },
                    "handoffs_answered": 2,
                    "appointments_booked": 1,
                    "first_response_seconds": 388.36
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/knowledge": {
      "get": {
        "summary": "List the files attached to an agent",
        "description": "The agent’s slice of the tenant knowledge library. A file lives in `/kb-files` and is ATTACHED here — the same file can back several agents.\n\nEach row carries `snippet`: the first 400 characters of the file’s first INGESTED chunk. `null` means the file has no chunks — still ingesting, or ingestion failed — which also means the agent cannot retrieve it, so it is a status signal and not just a missing preview.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Attached knowledge files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001",
                      "name": "Garantías 2026.pdf",
                      "status": "ingested",
                      "snippet": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía de fábrica…"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a new file, or attach an existing one",
        "description": "**One URL, two operations, chosen by content-type.** Send multipart with a `file` field to upload a new file and auto-attach it (25 MB cap); send JSON `{ kb_file_id }` to attach a file already in the library. Neither is a fallback for the other — a JSON body that is not a valid `{ kb_file_id }` is a 400 telling you to pick one.\n\nThe two also answer differently: the upload branch returns the created file row, the attach branch returns `{ attached: true, kb_file_id }`. Both are 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kb_file_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "kb_file_id"
                ]
              },
              "example": {
                "kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The uploaded file, or the attachment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "attached": true,
                    "kb_file_id": "a6a6a6a6-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/knowledge/{fileId}": {
      "delete": {
        "summary": "Detach a file from an agent",
        "description": "**Detaches, it does not delete.** The file stays in the tenant library and keeps backing every other agent it is attached to. To delete the file itself — which cascades to its attachments and its embedded chunks — use `DELETE /kb-files/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "fileId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/export": {
      "get": {
        "summary": "Export the agent’s full configuration",
        "description": "Everything the runtime assembles for this agent, built through the same assembler the runtime uses: the rendered system prompt, the gated tool catalogue with resolved \"when to use\" text, skills, KB files, workspace/business-hours/branch/branding/pipeline context, every parameter, and the staged draft. The best answer available to \"why did the agent do that\".\n\n**Neither format is the `{ data }` envelope.** The default is the bundle as a JSON document served as an attachment; `?format=zip` returns a readable folder tree (`instructions.md`, `skills/`, `knowledge/` with the real file bytes, `tools.json`). Available in all environments, tenant-scoped.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "zip"
              ],
              "description": "Omit for the JSON bundle; `zip` for a folder tree."
            },
            "required": false,
            "description": "Omit for the JSON bundle; `zip` for a folder tree.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The config bundle, as a JSON or zip attachment",
            "content": {
              "application/json": {
                "schema": {},
                "example": {
                  "agent": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas"
                  },
                  "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                  "tools": [
                    {
                      "name": "search_vehicle_stock",
                      "when_to_use": "Cuando el cliente pregunte por disponibilidad."
                    }
                  ],
                  "skills": [
                    {
                      "id": "a4a4a4a4-0000-4000-8000-000000000001",
                      "name": "Agendar hora"
                    }
                  ],
                  "knowledge": [
                    {
                      "id": "a6a6a6a6-0000-4000-8000-000000000001",
                      "name": "Garantías 2026.pdf"
                    }
                  ]
                }
              },
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/versions/{version}/rollback": {
      "post": {
        "summary": "Make a past version live again",
        "description": "Three steps in one call: load the version into the draft, PUBLISH it, and snapshot the result labelled `Rollback to v{n}` so history stays linear rather than branching. It also fires `ai_agent.publish`, so downstream consumers see a rollback as the publish it is.\n\nGuarded like a publish: the tenant `editable_window` applies and a rollback outside it is refused. Contrast the sibling `/versions/{version}/restore`, which only loads the version into the draft and leaves publishing to you — use that one if you want to look before going live.\n\n`{version}` is the version NUMBER, not the version row’s id. Anything in the draft at the time is overwritten.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The now-live agent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas",
                    "description": "Responde preguntas de stock y agenda visitas a sucursal.",
                    "status": "active",
                    "is_default": true,
                    "temperature": null,
                    "default_max_steps": 5,
                    "autonomy_level": "supervised",
                    "system_prompt": "Eres un asistente de ventas. Responde de forma breve y cercana.",
                    "knowledge_tags": [
                      "horarios",
                      "garantia"
                    ],
                    "tags": [],
                    "followups_enabled": true,
                    "allowed_url_prefixes": [],
                    "published_version_id": "a3a3a3a3-1000-4000-8000-000000000008",
                    "draft_system_prompt": null,
                    "draft_temperature": null,
                    "draft_max_steps": null,
                    "draft_tool_wiring": null,
                    "draft_skill_ids": null,
                    "draft_kb_file_ids": null,
                    "draft_knowledge_tags": null,
                    "draft_updated_at": null,
                    "created_at": "2026-08-04T19:07:05.226Z",
                    "updated_at": "2026-09-17T14:49:02.893Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests": {
      "get": {
        "summary": "List an agent's change requests",
        "description": "Newest first — the «Solicitudes» list on the agent page, which is what answers \"¿arreglaron lo de los guiones?\" without asking Vitrina. Each row carries its `display_id` (`SR-n`, per workspace — a label; every path here addresses a request by uuid), the `verbatim` the client said, the reporter (`reporter_kind` plus `reporter_name` when the filer is still a member with a display name), the linked scenario and evidence run once they exist, and the status.\n\nLifecycle: `received` → `grounded` → `reproduced` → `proposed` → `applied` → `verified`, plus two terminals that are outcomes rather than failures — `ya_cumple` (the scenario went green on its first run and the complaint was about an older version, so the request closes with that green run attached) and `harness` (the fix belongs to Vitrina's harness — turn loop, retries, handoff mechanics, channel behaviour — so the tenant reads \"esto lo arregla Vitrina\" with a reference instead of being sent to edit a skill that cannot fix it). `harness` is reachable from every non-terminal state; `ya_cumple` from `grounded` and `reproduced`. Everything else is a 400 naming both ends of the refused edge.\n\nRequires `ai_agents:read` AND `corrections:read` — a read, so the read pair, never the capture pair.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "received",
                "grounded",
                "reproduced",
                "proposed",
                "applied",
                "verified",
                "ya_cumple",
                "harness"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "conversation_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Change requests, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "grounded",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": null,
                      "evidence_run_id": null,
                      "verification_run_id": null,
                      "proposal_ids": [],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "File a change request (text or audio)",
        "description": "The «Nueva solicitud» form and the inbox turn action. Two content types, one behaviour:\n\n* `application/json` with `{ verbatim }` — what the client said, typed.\n* `multipart/form-data` with the recording in a part named `file` — webm / ogg / mp4 / m4a / aac / mp3 / wav, up to 20 MB (the `video/*` spelling of those containers is accepted too: a browser types a `.webm` picked from disk `video/webm` and its own MediaRecorder blob `audio/webm`, and only the audio track is read either way). The audio is normalised to OGG/Opus and transcribed, and THE TRANSCRIPT IS THE `verbatim`: nothing is stored that could not be turned into text. Send one or the other, never both.\n\n`reporter_kind` is `member` (the member's own observation) or `client_via_member` (the member relaying what a customer said — there is no feedback inbox and no channel, so a client's words always arrive through a member). `conversation_refs` is `[{ conversationId, messageId? }]`; in the multipart form it is a JSON-encoded string, because a form cannot carry an array.\n\nThe new request is born at `received` and stamps `received_against_version` — the `ai_agent_version.version_number` that was live at that moment, i.e. the version the client complained about, keyed exactly like `agent_scenario_run.agent_version_number` so the red gate can compare the two. Null when the agent has never been published.\n\nRequires `ai_agents:read` AND `corrections:write` — filing what a customer told you is a front-line act, and it does not grant anybody the agent's configuration.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "verbatim": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  },
                  "reporter_kind": {
                    "type": "string",
                    "enum": [
                      "member",
                      "client_via_member"
                    ]
                  },
                  "conversation_refs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "conversationId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "messageId": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "conversationId"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 20
                  }
                },
                "default": {},
                "additionalProperties": false
              },
              "example": {
                "verbatim": "No uses guiones largos, suena robótico.",
                "reporter_kind": "client_via_member",
                "conversation_refs": [
                  {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001"
                  }
                ]
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "reporter_kind": {
                    "type": "string"
                  },
                  "conversation_refs": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The filed change request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b5b5b5b5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "display_id": "SR-14",
                    "status": "received",
                    "verbatim": "No uses guiones largos, suena robótico.",
                    "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                    "reporter_kind": "client_via_member",
                    "reporter_name": "Camila (vendedora)",
                    "conversation_refs": [],
                    "scenario_id": null,
                    "evidence_run_id": null,
                    "verification_run_id": null,
                    "proposal_ids": [],
                    "grounding_attempts": [],
                    "no_repro_note": null,
                    "received_against_version": 6,
                    "override": null,
                    "harness_reference": null,
                    "tenant_message": null,
                    "created_at": "2026-09-20T14:00:00.000Z",
                    "updated_at": "2026-09-20T14:00:00.000Z",
                    "scenario": null,
                    "evidence_run": null,
                    "verification_run": null,
                    "override_by_name": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests/{crId}": {
      "get": {
        "summary": "Get one change request",
        "description": "The whole row: verbatim, reporter, confirmed conversation refs, the linked scenario and the failed run that is its evidence, the proposals it produced, the grounding attempts, any override recorded when somebody published past a red gate, and — for a `harness` terminal — the tenant-facing message and its reference.\n\nRequires `ai_agents:read` AND `corrections:read` — a read, so the read pair, never the capture pair.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The change request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b5b5b5b5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "display_id": "SR-14",
                    "status": "grounded",
                    "verbatim": "No uses guiones largos, suena robótico.",
                    "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                    "reporter_kind": "client_via_member",
                    "reporter_name": "Camila (vendedora)",
                    "conversation_refs": [
                      {
                        "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                        "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                      }
                    ],
                    "scenario_id": null,
                    "evidence_run_id": null,
                    "verification_run_id": null,
                    "proposal_ids": [],
                    "grounding_attempts": [
                      {
                        "outcome": "confirmed",
                        "path": "reviews",
                        "candidates": [
                          {
                            "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                            "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                            "quote": "listo, sin problema - te confirmo por aquí",
                            "why": "Coincide con el reclamo."
                          }
                        ]
                      }
                    ],
                    "no_repro_note": null,
                    "received_against_version": 6,
                    "override": null,
                    "harness_reference": null,
                    "tenant_message": null,
                    "created_at": "2026-09-20T14:00:00.000Z",
                    "updated_at": "2026-09-20T14:05:00.000Z",
                    "scenario": null,
                    "evidence_run": null,
                    "verification_run": null,
                    "override_by_name": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/ground": {
      "post": {
        "summary": "«Buscar dónde pasó» — find the conversations a complaint is about",
        "description": "Runs the `change-request-grounder` read-only tool loop over this tenant's own `conversation_review` summaries and typed events for this agent, ranked against the verbatim, opens the likely threads and answers with up to five candidates — each a conversation, the agent turn that matches (by message id) and the quote, plus one sentence saying why (ADR 0103 §4.3).\n\nWHEN THE TENANT HAS NO REVIEWS in the window, the loop falls back to reading that agent's most recent conversations directly. Which corpus answered is recorded on the attempt as `path` (`reviews` | `recent_conversations` | `none`).\n\nFULL-TEXT SEARCH OVER MESSAGE BODIES WAS REJECTED (§4.3): it finds words, and a complaint describes behaviour. The verbatim is treated as DATA throughout — it is the query, never an instruction — and every read is bounded before the first tool call: 30 days by default, at most 12 conversations opened, at most 60 messages of each. The run is metered as a platform agent like every other.\n\nThis call CHANGES NOTHING but the ledger: it appends one entry to `grounding_attempts` at `outcome: \"pending\"`. No ref is written and the status does not move until a human answers at `/ground/confirm`.\n\nA RUN THE AI SERVICE COULD NOT COMPLETE is NOT an empty answer: the attempt is recorded with `outcome: \"error\"`, and the call answers **502** `UPSTREAM_ERROR` with a fixed message. A 200 with no candidates would say \"we looked and it is not there\", which is a different and untrue statement.\n\nREFUSED WITH 400 when the request already names its conversations (a pasted ref, or the inbox action, skips grounding — there is nothing to search for) or when it has left `received`. Requires `ai_agents:read` AND `corrections:write` — the same pair as filing, because this asks \"which conversation did you mean\", not \"change the agent\".\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "window_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  }
                },
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request, the attempt just recorded and its candidates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "request": {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "received",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": null,
                      "evidence_run_id": null,
                      "verification_run_id": null,
                      "proposal_ids": [],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    },
                    "attempt": {
                      "outcome": "pending",
                      "path": "reviews",
                      "candidates": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                          "quote": "listo, sin problema - te confirmo por aquí",
                          "why": "El agente usó un guión largo en una respuesta de confirmación."
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/ground/confirm": {
      "post": {
        "summary": "Confirm (or reject) what the grounding run proposed",
        "description": "The human's answer to the last grounding run, and the only thing that turns it into a fact.\n\n* `{ refs: [{ conversationId, messageId }] }` — the candidates they recognised. They are written to `conversation_refs`, the attempt is marked `confirmed`, and the request walks `received → grounded` through the ordinary machine. Every ref must be one of the LAST run's candidates, message id included: confirming is answering a question that was asked, not a second way to attach an arbitrary conversation (that is the create path, where a pasted ref skips grounding).\n* `{ refs: [] }` — «Ninguna coincide». The attempt is marked `rejected` and the request STAYS at `received` with the run on record. Nothing is written to `conversation_refs`: a rejection is not the absence of refs, it is the statement that the loop did not find them.\n\n`meta.outcome` echoes which of the two happened. Requires `ai_agents:read` AND `corrections:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "refs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "conversationId": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "messageId": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "conversationId",
                        "messageId"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 5
                  }
                },
                "required": [
                  "refs"
                ],
                "additionalProperties": false
              },
              "example": {
                "refs": [
                  {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The change request after the answer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b5b5b5b5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "display_id": "SR-14",
                    "status": "grounded",
                    "verbatim": "No uses guiones largos, suena robótico.",
                    "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                    "reporter_kind": "client_via_member",
                    "reporter_name": "Camila (vendedora)",
                    "conversation_refs": [
                      {
                        "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                        "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                      }
                    ],
                    "scenario_id": null,
                    "evidence_run_id": null,
                    "verification_run_id": null,
                    "proposal_ids": [],
                    "grounding_attempts": [
                      {
                        "outcome": "confirmed",
                        "path": "reviews",
                        "candidates": [
                          {
                            "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                            "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                            "quote": "listo, sin problema - te confirmo por aquí",
                            "why": "Coincide con el reclamo."
                          }
                        ]
                      }
                    ],
                    "no_repro_note": null,
                    "received_against_version": 6,
                    "override": null,
                    "harness_reference": null,
                    "tenant_message": null,
                    "created_at": "2026-09-20T14:00:00.000Z",
                    "updated_at": "2026-09-20T14:05:00.000Z",
                    "scenario": null,
                    "evidence_run": null,
                    "verification_run": null,
                    "override_by_name": null
                  },
                  "meta": {
                    "outcome": "confirmed"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/transition": {
      "post": {
        "summary": "Move a change request along its lifecycle",
        "description": "Lifecycle: `received` → `grounded` → `reproduced` → `proposed` → `applied` → `verified`, plus two terminals that are outcomes rather than failures — `ya_cumple` (the scenario went green on its first run and the complaint was about an older version, so the request closes with that green run attached) and `harness` (the fix belongs to Vitrina's harness — turn loop, retries, handoff mechanics, channel behaviour — so the tenant reads \"esto lo arregla Vitrina\" with a reference instead of being sent to edit a skill that cannot fix it). `harness` is reachable from every non-terminal state; `ya_cumple` from `grounded` and `reproduced`. Everything else is a 400 naming both ends of the refused edge.\n\nTHE SCOPE SPLIT. `received → grounded` takes the same pair as filing (`ai_agents:read` AND `corrections:write`): it says \"this is the conversation I meant\", not \"change the agent\". EVERY other target additionally requires `ai_agents:write`, and a caller without it gets a 403 naming the scope.\n\nThe write is a compare-and-set against the status that was read, so two people pressing at once cannot both win — the loser gets a 400 saying the request is no longer where they thought it was.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "to": {
                    "type": "string",
                    "enum": [
                      "received",
                      "grounded",
                      "reproduced",
                      "proposed",
                      "applied",
                      "verified",
                      "ya_cumple",
                      "harness"
                    ]
                  }
                },
                "required": [
                  "to"
                ],
                "additionalProperties": false
              },
              "example": {
                "to": "reproduced"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The change request after the move",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b5b5b5b5-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                    "display_id": "SR-14",
                    "status": "reproduced",
                    "verbatim": "No uses guiones largos, suena robótico.",
                    "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                    "reporter_kind": "client_via_member",
                    "reporter_name": "Camila (vendedora)",
                    "conversation_refs": [
                      {
                        "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                        "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                      }
                    ],
                    "scenario_id": null,
                    "evidence_run_id": null,
                    "verification_run_id": null,
                    "proposal_ids": [],
                    "grounding_attempts": [
                      {
                        "outcome": "confirmed",
                        "path": "reviews",
                        "candidates": [
                          {
                            "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                            "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                            "quote": "listo, sin problema - te confirmo por aquí",
                            "why": "Coincide con el reclamo."
                          }
                        ]
                      }
                    ],
                    "no_repro_note": null,
                    "received_against_version": 6,
                    "override": null,
                    "harness_reference": null,
                    "tenant_message": null,
                    "created_at": "2026-09-20T14:00:00.000Z",
                    "updated_at": "2026-09-20T14:05:00.000Z",
                    "scenario": null,
                    "evidence_run": null,
                    "verification_run": null,
                    "override_by_name": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/scenario": {
      "post": {
        "summary": "«Crear escenario» — author the eval from the request and run it",
        "description": "ONE call does four things, because all four are the same act and half of it is worse than none (ADR 0103 §4.2):\n\n1. The `scenario-builder` writes the scenario — `from: \"conversation\"` over the FIRST confirmed ref (the builder masks phones, e-mails and RUTs out of the transcript before the model sees it), or `from: \"description\"` with the verbatim when the request names no conversation. EITHER WAY the verbatim is the operator `note`, so the client's own words end up in `scenario.source.note`; when the behaviour was confirmed in more than one thread the note says so, because a complaint seen four times is a class and deserves a check that guards the class.\n2. The scenario is saved ACTIVE and TAGGED with the request `SR-n`, which is how the Evals tab and the «Solicitudes» list find each other.\n3. It joins the agent's golden suite — by construction when the suite has the usual empty filter (every ACTIVE scenario), by appending to `scenario_ids` when the suite names an explicit list.\n4. ONE run is enqueued — never the scenario's own `repeats`, because this is evidence and not a flake measurement — against the version the client complained about: `live` when `received_against_version` is a number, `draft` when it is null (the agent was never published, so the draft IS the thing under repair). `scenario_id` and `evidence_run_id` land on the request.\n\nThe run is ASYNC: the answer carries it `queued`, and the verdict is applied by the scenario-run worker when it finishes (or on demand at `…/scenario/evaluate`).\n\nREFUSED WITH 400 when the request is not `grounded` (nobody has settled which conversations it is about) or already links a scenario (that is «Refinar»). A body the builder could not make valid twice is a **422** with `code: \"builder_invalid\"` and the zod issues, exactly as `POST /ai-agents/{id}/scenarios/build` answers it. Requires `ai_agents:read` AND `corrections:write` AND `ai_agents:write` — it authors and saves an eval, which is what the scenario routes take.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The request, the scenario, the enqueued run and where it ran",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "request": {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "reproduced",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "evidence_run_id": "b5b5b5b5-2000-4000-8000-000000000001",
                      "verification_run_id": null,
                      "proposal_ids": [],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    },
                    "scenario": {
                      "id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "name": "SR-14 — sin guiones largos",
                      "tags": [
                        "SR-14"
                      ]
                    },
                    "run": {
                      "id": "b5b5b5b5-2000-4000-8000-000000000001",
                      "status": "queued"
                    },
                    "ran_against": "live"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/scenario/evaluate": {
      "post": {
        "summary": "Re-read the evidence run and re-apply the red-gate rule",
        "description": "§4.2's rule, applied on demand and IDEMPOTENTLY:\n\n* run `failed` — the complaint reproduces: `grounded → reproduced`, and «no reprodujo» is cleared.\n* run `passed` or `partial` and the complaint PREDATES the live version — the agent already complies: the request closes as `ya_cumple` **with the green run still attached**, because a verdict nobody can open is not a verdict.\n* run `passed` or `partial` otherwise — the request STAYS at `grounded`, `no_repro_note` records «no reprodujo» with a timestamp, and «Refinar» is what the surface offers next. A scenario authored from a complaint that passes on its first run has captured something else.\n* run `error` — nothing was learned. The request stays exactly where it was and the note says the gateway refused, because \"we ran it and it did not reproduce\" is a different and untrue statement.\n* run `queued`/`running` — `meta.outcome: \"pending\"` and nothing changes.\n\nThe scenario-run worker applies the same rule through the same function the moment a run finishes; this exists for when that hook did not run (the evals pool was down, the process was killed between the finish and the hook). 400 when the request has no run to evaluate. Same scopes as authoring: the rule MOVES the request, and every target past `grounded` takes `ai_agents:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request after the rule, the run, and the outcome",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "request": {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "reproduced",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "evidence_run_id": "b5b5b5b5-2000-4000-8000-000000000001",
                      "verification_run_id": null,
                      "proposal_ids": [],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    },
                    "run": {
                      "id": "b5b5b5b5-2000-4000-8000-000000000001",
                      "status": "failed"
                    },
                    "meta": {
                      "outcome": "reproduced"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/propose": {
      "post": {
        "summary": "«Proponer arreglo» — the investigator writes the fix from the red run",
        "description": "Runs the `finding-investigator`'s READ-ONLY tool loop over this request's evidence and stores what it proposes, then walks the request `reproduced → proposed` (ADR 0103 §4.5).\n\nThe evidence set is the REQUEST, not a finding: the verbatim (fenced and declared untrusted — it is a customer’s words reaching a tool-using model), the conversations a human confirmed, **the RED RUN’s transcript and judge verdict** — the controlled reproduction §4.2 spent a model call and a run to produce — and the agent’s draft-aware configuration with what is available but not attached.\n\nThe proposals go through the Coach’s own `validateProposals` / `persistProposalsDeduped`: an unknown action is dropped, `applyable` is forced from the canonical set (the model never decides it), and a repeat of an already-open proposal for this agent bumps it instead of inserting a twin. They land with `change_request_id` set and their ids appended to the request’s `proposal_ids`.\n\n**Every proposal carries a `surface`** derived from its action — `prompt`, `skill`, `knowledge`, `tool`, `model`, `handoff`, `routing`, `integration`, `template`, `harness`, `feature` — which is the vocabulary §4.5 makes the ledger countable in: how often the answer is \"this one is Vitrina’s\", and whether the prompt is doing work the knowledge base should be doing.\n\nREFUSED WITH 400 unless the request is `reproduced` AND its scenario run went red against the version complained about — the same `assertRedRunEvidence` the transition surface asks, called BEFORE the model runs so a request without evidence costs nothing. A run the AI service could not complete is a **502** `UPSTREAM_ERROR` with a fixed message («try again in a few minutes») and moves nothing — \"we looked and there is no fix\" is a different and untrue statement. Synchronous: somebody pressed a button on their own request. Requires `ai_agents:read` AND `corrections:write` AND `ai_agents:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The request at `proposed`, the proposals with their surfaces, and the investigation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "request": {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "proposed",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "evidence_run_id": "b5b5b5b5-2000-4000-8000-000000000001",
                      "verification_run_id": null,
                      "proposal_ids": [
                        "b5b5b5b5-3000-4000-8000-000000000001"
                      ],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    },
                    "proposals": [
                      {
                        "id": "b5b5b5b5-3000-4000-8000-000000000001",
                        "surface": "prompt",
                        "action": "edit_system_prompt",
                        "summary": "Agrega una instrucción explícita: nunca usar guiones largos en las respuestas.",
                        "applyable": true
                      }
                    ],
                    "summary": "El agente usa guiones largos porque su prompt no dice lo contrario."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/ai-agents/{id}/change-requests/{crId}/scenario/refine": {
      "post": {
        "summary": "«Refinar» — rewrite the scenario that did not reproduce and re-run it",
        "description": "The builder's `refine` step over the request's current scenario, then a fresh run (ADR 0103 §4.2: a first-run pass \"leaves the request at `grounded` with a note and offers the builder's refine step\").\n\n`note` is what the builder is told to change and DEFAULTS TO THE VERBATIM, which is the correct first refine: a scenario that went green on a real complaint usually missed the point of what the client said. The stored body is replaced with the refined one (the `SR-n` tag is re-applied rather than trusted), `evidence_run_id` points at the new run and `no_repro_note` is CLEARED — the old verdict was about a scenario that no longer exists in that shape.\n\nOffered while the request is still `grounded` and it links a scenario; anything else is a 400. Past `grounded` there is a red run somebody is acting on, and replacing the scenario under it would move the evidence out from under a proposal. Same scopes as authoring; the same 422 contract when the builder cannot produce a valid body twice.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "crId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 5000
                  }
                },
                "default": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request, the refined scenario, the new run and what moved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "request": {
                      "id": "b5b5b5b5-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "display_id": "SR-14",
                      "status": "grounded",
                      "verbatim": "No uses guiones largos, suena robótico.",
                      "reporter_member_id": "11111111-0000-4000-8000-000000000001",
                      "reporter_kind": "client_via_member",
                      "reporter_name": "Camila (vendedora)",
                      "conversation_refs": [
                        {
                          "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                          "messageId": "bbbbbbbb-1000-4000-8000-000000000001"
                        }
                      ],
                      "scenario_id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "evidence_run_id": "b5b5b5b5-2000-4000-8000-000000000002",
                      "verification_run_id": null,
                      "proposal_ids": [],
                      "grounding_attempts": [
                        {
                          "outcome": "confirmed",
                          "path": "reviews",
                          "candidates": [
                            {
                              "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                              "messageId": "bbbbbbbb-1000-4000-8000-000000000001",
                              "quote": "listo, sin problema - te confirmo por aquí",
                              "why": "Coincide con el reclamo."
                            }
                          ]
                        }
                      ],
                      "no_repro_note": null,
                      "received_against_version": 6,
                      "override": null,
                      "harness_reference": null,
                      "tenant_message": null,
                      "created_at": "2026-09-20T14:00:00.000Z",
                      "updated_at": "2026-09-20T14:05:00.000Z",
                      "scenario": null,
                      "evidence_run": null,
                      "verification_run": null,
                      "override_by_name": null
                    },
                    "scenario": {
                      "id": "b5b5b5b5-1000-4000-8000-000000000001",
                      "name": "SR-14 — sin guiones largos (refinado)",
                      "tags": [
                        "SR-14"
                      ]
                    },
                    "run": {
                      "id": "b5b5b5b5-2000-4000-8000-000000000002",
                      "status": "queued"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/ai-agents/{id}/change-request-stats": {
      "get": {
        "summary": "One agent's change-request ledger",
        "description": "ADR 0103 §4.5's two questions, over one agent. Neither existed before this endpoint, and the ADR says why each is worth counting:\n\n* **`harness.ratio`** — requests that terminated at `harness` (the turn loop, tool execution, retries, handoff mechanics and channel behaviour Vitrina owns, §1.6) over all requests filed in the window. It is the measure of **where the product is weak**: a workspace whose complaints keep landing there is reporting a defect in the harness, not asking for prompt edits.\n* **`appliedBySurface` / `appliedBySurfaceOverTime`** — the fixes that were APPLIED in the window, by surface (`prompt`, `skill`, `knowledge`, `tool`, `model`, `handoff`, `routing`, `integration`, `template`, `harness`, `feature`), as a total and bucketed by **ISO week**. A month of fixes that are all `prompt` is a knowledge base nobody is filling.\n\nTWO CLOCKS, deliberately: a request is counted by when it was FILED, an applied fix by when it was APPLIED — a fix landed this week on a complaint from last quarter belongs to this week. `byStatus` always carries all eight states, zero-filled, so «harness: 0» is visible rather than absent. `days` defaults to 90 (max 365): the quantity is a trend, and a workspace files a handful of requests a month.\n\nRequires `ai_agents:read` AND `corrections:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "required": false,
            "name": "days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Totals by status, the harness ratio, and applied fixes by surface and ISO week",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "days": 90,
                    "total": 14,
                    "byStatus": {
                      "received": 2,
                      "grounded": 1,
                      "reproduced": 0,
                      "proposed": 1,
                      "applied": 3,
                      "verified": 5,
                      "ya_cumple": 1,
                      "harness": 1
                    },
                    "harness": {
                      "terminated": 1,
                      "ratio": 0.0714
                    },
                    "appliedBySurface": [
                      {
                        "surface": "prompt",
                        "count": 5
                      },
                      {
                        "surface": "skill",
                        "count": 2
                      },
                      {
                        "surface": "knowledge",
                        "count": 1
                      }
                    ],
                    "appliedBySurfaceOverTime": [
                      {
                        "bucket": "2026-W37",
                        "surface": "prompt",
                        "count": 2
                      },
                      {
                        "bucket": "2026-W37",
                        "surface": "skill",
                        "count": 1
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "prompt",
                        "count": 3
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "skill",
                        "count": 1
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "knowledge",
                        "count": 1
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/change-requests/stats": {
      "get": {
        "summary": "The workspace's change-request ledger",
        "description": "The same two aggregates as `GET /ai-agents/{id}/change-request-stats`, across EVERY agent of the workspace. \"Where is the product weak here\" is a question about the workspace: one agent's harness ratio over a quarter is a handful of rows, and a tenant running three agents wants the number across them.\n\n**There is no platform-wide variant on purpose.** ADR 0103 §1.6: memberships are `owner | admin | supervisor | agent`, there is no staff role and no superadmin, and everything in this product is tenant-facing by construction. Vitrina's cross-tenant read of the same two numbers is the service-role, read-only script `scripts/ops/change-request-stats.ts`.\n\nRequires `ai_agents:read` AND `corrections:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "required": false,
            "name": "days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Totals by status, the harness ratio, and applied fixes by surface and ISO week",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "days": 90,
                    "total": 14,
                    "byStatus": {
                      "received": 2,
                      "grounded": 1,
                      "reproduced": 0,
                      "proposed": 1,
                      "applied": 3,
                      "verified": 5,
                      "ya_cumple": 1,
                      "harness": 1
                    },
                    "harness": {
                      "terminated": 1,
                      "ratio": 0.0714
                    },
                    "appliedBySurface": [
                      {
                        "surface": "prompt",
                        "count": 5
                      },
                      {
                        "surface": "skill",
                        "count": 2
                      },
                      {
                        "surface": "knowledge",
                        "count": 1
                      }
                    ],
                    "appliedBySurfaceOverTime": [
                      {
                        "bucket": "2026-W37",
                        "surface": "prompt",
                        "count": 2
                      },
                      {
                        "bucket": "2026-W37",
                        "surface": "skill",
                        "count": 1
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "prompt",
                        "count": 3
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "skill",
                        "count": 1
                      },
                      {
                        "bucket": "2026-W38",
                        "surface": "knowledge",
                        "count": 1
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/agent-evals/activation": {
      "get": {
        "summary": "The workspace's evals opt-in and price",
        "description": "Evals are charged per completed run (passed, partial or failed) at the rate card's price — today 0.0025 UF per run — as a usage line on the monthly invoice («Evaluaciones del agente»). They are OFF until a workspace owner/admin turns them on and accepts that price; until then every run endpoint answers 402 `AGENT_EVALS_NOT_ENABLED` and nothing is queued or billed.\n\nReturns whether evals are on, the current price, and who accepted it, when and at what price.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "The opt-in",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean",
                      "description": "Evals may run in this workspace now."
                    },
                    "state": {
                      "type": "string",
                      "enum": [
                        "enabled",
                        "not_enabled",
                        "disabled",
                        "suspended",
                        "internal",
                        "sandbox"
                      ]
                    },
                    "price": {
                      "type": "object",
                      "properties": {
                        "uf_per_run": {
                          "type": "number",
                          "description": "UF per completed eval run."
                        },
                        "label": {
                          "type": "string",
                          "description": "The same price as a person reads it: «0,0025»."
                        }
                      },
                      "required": [
                        "uf_per_run",
                        "label"
                      ]
                    },
                    "acceptance": {
                      "type": [
                        "object",
                        "null"
                      ],
                      "properties": {
                        "accepted_by": {
                          "type": "string"
                        },
                        "accepted_by_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "accepted_at": {
                          "type": "string"
                        },
                        "uf_per_run": {
                          "type": "number"
                        },
                        "rate_card": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "accepted_by",
                        "accepted_by_name",
                        "accepted_at",
                        "uf_per_run",
                        "rate_card"
                      ],
                      "description": "Who turned evals on, when, and at what price."
                    },
                    "billable": {
                      "type": "boolean",
                      "description": "Runs are invoiced (false for internal or complimentary workspaces)."
                    },
                    "can_enable": {
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "enabled",
                    "state",
                    "price",
                    "acceptance",
                    "billable",
                    "can_enable"
                  ]
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "state": "enabled",
                    "price": {
                      "uf_per_run": 0.0025,
                      "label": "0,0025"
                    },
                    "acceptance": {
                      "accepted_by": "10000000-0000-4000-8000-000000000001",
                      "accepted_by_name": "Ana Pérez",
                      "accepted_at": "2026-10-17T16:00:00.000Z",
                      "uf_per_run": 0.0025,
                      "rate_card": "standard"
                    },
                    "billable": true,
                    "can_enable": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Turn evals on and accept the per-run price",
        "description": "Evals are charged per completed run (passed, partial or failed) at the rate card's price — today 0.0025 UF per run — as a usage line on the monthly invoice («Evaluaciones del agente»). They are OFF until a workspace owner/admin turns them on and accepts that price; until then every run endpoint answers 402 `AGENT_EVALS_NOT_ENABLED` and nothing is queued or billed.\n\nOwner/admin members only (403 otherwise, including an API key: a price is accepted by a person). `accept_uf_per_run` must be the current price, else 409 `AGENT_EVALS_PRICE_CHANGED`. Records who accepted, when and at what price. Idempotent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "accept_uf_per_run": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "The UF price per completed eval run the admin accepted — must be the current one."
                  }
                },
                "required": [
                  "accept_uf_per_run"
                ],
                "additionalProperties": false
              },
              "example": {
                "accept_uf_per_run": 0.0025
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The opt-in, now `enabled`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "state": "enabled",
                    "price": {
                      "uf_per_run": 0.0025,
                      "label": "0,0025"
                    },
                    "acceptance": {
                      "accepted_by": "10000000-0000-4000-8000-000000000001",
                      "accepted_by_name": "Ana Pérez",
                      "accepted_at": "2026-10-17T16:00:00.000Z",
                      "uf_per_run": 0.0025,
                      "rate_card": "standard"
                    },
                    "billable": true,
                    "can_enable": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      },
      "delete": {
        "summary": "Turn evals off",
        "description": "Immediate: no new run is queued after this. Runs already queued finish and bill at the accepted price. The acceptance stays on record.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "responses": {
          "200": {
            "description": "The opt-in, now `disabled`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enabled": false,
                    "state": "disabled",
                    "price": {
                      "uf_per_run": 0.0025,
                      "label": "0,0025"
                    },
                    "acceptance": {
                      "accepted_by": "10000000-0000-4000-8000-000000000001",
                      "accepted_by_name": "Ana Pérez",
                      "accepted_at": "2026-10-17T16:00:00.000Z",
                      "uf_per_run": 0.0025,
                      "rate_card": "standard"
                    },
                    "billable": true,
                    "can_enable": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/publish-gate": {
      "get": {
        "summary": "What the agent's golden suite last said (informational)",
        "description": "INFORMATIONAL ONLY since app#3975: publishing never runs, waits on or is blocked by evals. Reads the agent's ENABLED GOLDEN suite and nothing else.\n\n- `none` — no enabled golden suite. Never blocks: a workspace that has not opted into gating is not gated.\n- `pending` — a golden suite run is queued or running. Warn only; blocking here would mean \"press publish again in four minutes\".\n- `stale` — the newest COMPLETED golden run predates the agent's `draft_updated_at` (or there is none). Checked BEFORE the policy: a red verdict about a draft that no longer exists is not evidence about the one being published.\n- `blocked` — that run has hard fails (when `policy.block_publish_on_hard_fail`) or a `pass_rate` under `policy.min_pass_rate`.\n- `ready` — otherwise.\n\n`reasons` are stable snake_case codes (`no_golden_suite`, `suite_run_in_flight`, `no_completed_run`, `stale_draft`, `hard_fails`, `pass_rate_below_min`) so the dialog can localize them. `failing` lists the red scenarios of the last completed run with their `run_id`s.\n\n`blocked` and `stale` no longer refuse a publish.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The publish gate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "gate": "clear",
                    "suite": {
                      "id": "b6b6b6b6-0000-4000-8000-000000000001",
                      "name": "Golden",
                      "kind": "golden"
                    },
                    "last_run": {
                      "pass_rate": 91.2,
                      "hard_fails": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/findings/{findingId}": {
      "patch": {
        "summary": "Mark a finding addressed, dismissed (with a note) or reopen it",
        "description": "Sets `status` to `addressed`, `dismissed` or `open`. `addressed` and `dismissed` stamp who decided and when; `dismissed` also stores the optional `note` as `dismiss_note`. Reopening with `open` clears those stamps. Recorded in the audit log and refreshes the agent's Coach notices. Needs the `corrections:write` scope; 404 when the finding does not belong to the agent in this workspace. Answers the updated finding.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Mejoras"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "findingId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "addressed",
                      "dismissed"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 1000
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false
              },
              "example": {
                "status": "dismissed",
                "note": "Es un caso aislado de una campaña ya terminada."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated finding",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7b7b7b7b-0000-4000-8000-000000000011",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "ai_agent_id": "7b7b7b7b-0000-4000-8000-000000000001",
                    "kind": "missing_knowledge",
                    "topic_key": "horarios-de-atencion",
                    "title": "El agente no sabe los horarios de atención de la sucursal",
                    "description": "Varios clientes preguntaron por el horario de sábado sin obtener respuesta.",
                    "status": "dismissed",
                    "first_seen_at": "2026-09-20T15:00:00.000Z",
                    "last_seen_at": "2026-10-08T18:30:00.000Z",
                    "occurrence_count": 7,
                    "sample_conversation_ids": [
                      "bbbbbbbb-0000-4000-8000-000000000001",
                      "bbbbbbbb-0000-4000-8000-000000000002"
                    ],
                    "investigation": null,
                    "investigated_at": null,
                    "investigation_error": null,
                    "addressed_at": null,
                    "addressed_by": null,
                    "dismissed_at": "2026-10-09T10:00:00.000Z",
                    "dismissed_by": "11111111-0000-4000-8000-000000000001",
                    "dismiss_note": "Es un caso aislado de una campaña ya terminada.",
                    "created_at": "2026-09-20T15:00:00.000Z",
                    "updated_at": "2026-10-09T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/handoff": {
      "post": {
        "summary": "Hand the conversation off to a human team",
        "description": "Flips the handling axis to `human` so the thread enters the agent inbox, opening or routing a ticket when the workspace has a team for it — the assignment rules decide who, which is why this is not the same call as `/assign` with a member id. Works with or without a ticket. An optional `reason` is recorded in the audit trail.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason": "El cliente pide hablar con una persona"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The handoff result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "handler": "human",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "team_id": "cccccccc-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/handoff-feedback": {
      "post": {
        "summary": "The assigned human's answer to \"could the agent have avoided this handoff?\"",
        "description": "`skipped` only records that the card was dismissed; any other verdict queues a review that reads the note as first-hand evidence.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Mejoras"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "verdict": {
                    "type": "string",
                    "enum": [
                      "missing_knowledge",
                      "missing_capability",
                      "wrong_behavior",
                      "correct_handoff",
                      "skipped"
                    ]
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "required": [
                  "verdict"
                ],
                "additionalProperties": false
              },
              "example": {
                "verdict": "missing_knowledge",
                "note": "Preguntó por el horario de sábado y el agente no tenía el dato."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The recorded feedback",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "feedback": {
                      "verdict": "missing_knowledge",
                      "note": "Preguntó por el horario de sábado y el agente no tenía el dato.",
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "at": "2026-10-09T10:00:00.000Z"
                    },
                    "review_status": "pending"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/availability": {
      "get": {
        "summary": "Open slots",
        "description": "Answers `{ configured, timezone, slots }`. `configured: false` means the workspace has never saved a schedule config, so these slots were generated from the table’s default knobs on the workspace’s own opening hours. It is a LABEL, not an error — the slots are real and bookable, and the right treatment is to render them while offering `PUT /appointments/config`, never to hide them. An empty `slots` list means what it says at any value of `configured`: nothing is open in the window you asked for.\n\nThe window is clamped on both sides regardless of `from`/`to`: it never starts before now plus `min_lead_minutes` and never runs past `booking_horizon_days`. Ask for next year and you get the horizon, not an error.\n\n`appointment_type_id` sets the slot LENGTH from that type’s duration while the grid step stays `slot_minutes`. An unknown or inactive type is tolerated here and falls back to the grid size — but booking validates it strictly, so a slot offered with a bad type id will be refused at `POST /appointments`. `vehicle_id` additionally excludes windows where that specific car is already out. `property_id` (real estate) reads the calendar of that property’s corredor in charge and, when the type is `property_exclusive`, excludes windows where another buyer already visits that property. `project_id` (inmobiliaria) reads a proyecto’s sala de ventas: the sala’s own opening hours, and only the hours where one of the proyecto’s ejecutivos is free.\n\nResults are memoised in-process for about 30 seconds, so a slot can be offered here and gone by the time you book. That is bounded and safe rather than a race: the booking transaction re-validates capacity, so stale availability can only produce a clean refusal, never a double booking. Registered before `/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "property_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "owner_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "appointment_type_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ configured, timezone, slots }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "timezone": "America/Santiago",
                    "slots": [
                      {
                        "startsAt": "2026-10-01T13:30:00.000Z",
                        "endsAt": "2026-10-01T14:15:00.000Z",
                        "label": "2026-10-01 10:30",
                        "labelLong": "jueves, 1 de octubre, 10:30"
                      },
                      {
                        "startsAt": "2026-10-01T14:00:00.000Z",
                        "endsAt": "2026-10-01T14:45:00.000Z",
                        "label": "2026-10-01 11:00",
                        "labelLong": "jueves, 1 de octubre, 11:00"
                      }
                    ],
                    "horizon_end": "2026-10-15T13:43:45.308Z",
                    "booking_horizon_days": 14
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/booking-agents": {
      "get": {
        "summary": "Which agents can book on the agenda",
        "description": "Every AI agent of the workspace and how far it can book on the native agenda: `booking` is `on` (every scheduling tool on, or the rest switched off by the owner), `partial`, `off`, or `switched_off` (the owner switched every one off). `missing_tools` are the scheduling tools the agent has neither on nor switched off — what `POST` would add. `agenda_configured` is the same flag as `GET /appointments/config`; `has_open_hours` is false when the effective week has no open interval, i.e. an agent could not offer a single slot. Scope `ai_agents:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "responses": {
          "200": {
            "description": "`{ agenda_configured, has_open_hours, agents }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "agenda_configured": true,
                  "has_open_hours": true,
                  "agents": [
                    {
                      "id": "6f1c2a4e-0b7d-4c1e-9a55-3d2e8f7a9b10",
                      "name": "Vera",
                      "status": "active",
                      "booking": "off",
                      "missing_tools": [
                        "list_appointment_types",
                        "find_slots",
                        "book_appointment",
                        "reschedule_appointment",
                        "cancel_appointment",
                        "list_appointments"
                      ],
                      "switched_off_tools": []
                    },
                    {
                      "id": "0a9e7c3b-5d2f-4e8a-b1c6-7f4d3e2a1b09",
                      "name": "Tomás",
                      "status": "active",
                      "booking": "on",
                      "missing_tools": [],
                      "switched_off_tools": [
                        "cancel_appointment"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Enable booking on the agents the owner confirmed",
        "description": "Adds the scheduling tools (`list_appointment_types`, `find_slots`, `book_appointment`, `reschedule_appointment`, `cancel_appointment`, `list_appointments`) to each agent in `ai_agent_ids`, live. A tool the owner switched off on an agent stays off (`switched_off_tools`). An agent with a pending draft tool wiring also gets the tools staged into the draft, so publishing it keeps them (`draft_updated`). Changing an agent is the owner’s decision: call this only with agents the owner confirmed. `409 agenda_not_configured` until a scheduling policy is saved (`PUT /appointments/config`); `404` if any id is not one of the workspace’s agents (nothing is written). Scope `ai_agents:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ai_agent_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 50
                  }
                },
                "required": [
                  "ai_agent_ids"
                ]
              },
              "example": {
                "ai_agent_ids": [
                  "6f1c2a4e-0b7d-4c1e-9a55-3d2e8f7a9b10"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ has_open_hours, agents }` — per agent, the tools this call turned on",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "has_open_hours": true,
                  "agents": [
                    {
                      "id": "6f1c2a4e-0b7d-4c1e-9a55-3d2e8f7a9b10",
                      "name": "Vera",
                      "tools_added": [
                        "list_appointment_types",
                        "find_slots",
                        "book_appointment",
                        "reschedule_appointment",
                        "cancel_appointment",
                        "list_appointments"
                      ],
                      "switched_off_tools": [],
                      "draft_updated": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/appointments/config": {
      "get": {
        "summary": "The scheduling configuration",
        "description": "The workspace’s EFFECTIVE booking rules: timezone, weekly `business_hours` (raw) and `effective_business_hours` (what booking enforces), slot grid, buffer, lead time, horizon, staff capacity, hold TTL, reminder lead, and the `arrival_board_enabled` reception switch. Never null: a workspace that configured nothing gets the table defaults on the workspace clock.\n\n`configured` is `true` only once a scheduling POLICY has been saved — a `PUT` carrying any field other than `arrival_board_enabled`. It is NOT \"a stored row exists\": saving `arrival_board_enabled` alone creates the row and leaves `configured: false` (default knobs, the agent's native `appointments` tools stay gated). It is the same flag `availability` reports.\n\n`arrival_board_enabled` is the stored «Sala de espera» switch (`false` when never saved).\n\n`next_day_cutoff_local` / `next_day_earliest_local` (\"HH:MM\", tenant clock, `null` = off) are the overnight booking cutoff the AI agent enforces for EVERY booking engine (native, Medilink/Dentalink, Reservo, native clinic): from the cutoff on day X-1, slots on day X before the earliest time are hidden from `find_slots` and refused by `book_appointment` / `hold_slot` / `reschedule_appointment` with `too_soon`. Like `arrival_board_enabled` they are NOT scheduling policy: saving them alone never marks the agenda `configured`. `min_lead_minutes` is enforced by the same agent tools on every engine.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "responses": {
          "200": {
            "description": "The effective schedule config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "timezone": "America/Santiago",
                    "business_hours": {
                      "mon": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "tue": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "wed": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "thu": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "fri": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "17:00"
                        ]
                      ]
                    },
                    "effective_business_hours": {
                      "mon": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "tue": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "wed": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "thu": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "fri": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "17:00"
                        ]
                      ]
                    },
                    "business_hours_source": "config",
                    "holidays": [
                      "2026-09-18",
                      "2026-09-19",
                      "2026-10-12",
                      "2026-10-31"
                    ],
                    "slot_minutes": 30,
                    "buffer_minutes": 10,
                    "min_lead_minutes": 120,
                    "booking_horizon_days": 14,
                    "staff_capacity": 2,
                    "vehicle_exclusive": false,
                    "owner_capacity": 1,
                    "hold_ttl_minutes": 15,
                    "reminder_lead_minutes": 120,
                    "deposit_messages_enabled": true,
                    "deposit_reminder_lead_minutes": 120,
                    "sales_team_id": "cccccccc-0000-4000-8000-000000000001",
                    "next_day_cutoff_local": "18:00",
                    "next_day_earliest_local": "11:00",
                    "arrival_board_enabled": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Create or update the scheduling configuration",
        "description": "An upsert: the first call creates the config, later ones patch the fields present. Everything is optional, so this is safe to call with one field.\n\nThis doubles as the **connect flow** for scheduling. Saving a POLICY field (anything but `arrival_board_enabled`) marks the agenda `configured` and seeds the per-tenant scheduling tool rows, so the booking tools become wireable to an agent the moment scheduling exists. That seeding is best-effort and its failure does not fail the call. `arrival_board_enabled` alone is a reception setting: it creates or patches the row but never marks the agenda configured and seeds nothing.\n\n`business_hours` is a weekday map to a list of `[\"HH:MM\", \"HH:MM\"]` ranges — a list, so a lunch break is two ranges on one day. It is REPLACED wholesale, and it is a different shape from the workspace `business_hours` used for SLA coverage; these are opening hours for booking, not the support clock.\n\n`slot_minutes` is the grid step (also for a Reservo agenda once a policy is saved; without one, Reservo slots step by the service duration), `buffer_minutes` the gap kept between visits, `staff_capacity` how many can overlap at once, `vehicle_exclusive` whether one car can be on only one active visit at a time (default false: several customers may book the same car at the same hour), `owner_capacity` how many appointments one professional / executive may carry at the same time (default 1). Changing them affects future availability only — appointments already booked are never re-validated or evicted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "timezone": {
                    "type": "string",
                    "minLength": 1
                  },
                  "business_hours": {
                    "type": "object",
                    "properties": {
                      "mon": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "tue": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "wed": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "thu": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "fri": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "sat": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      },
                      "sun": {
                        "type": "array",
                        "items": {
                          "type": "array",
                          "prefixItems": [
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            },
                            {
                              "type": "string",
                              "pattern": "^\\d{1,2}:\\d{2}$"
                            }
                          ]
                        }
                      }
                    }
                  },
                  "slot_minutes": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "buffer_minutes": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "min_lead_minutes": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "booking_horizon_days": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "staff_capacity": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "vehicle_exclusive": {
                    "type": "boolean"
                  },
                  "owner_capacity": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "default_overbook_limit": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 50
                  },
                  "arrival_board_enabled": {
                    "type": "boolean"
                  },
                  "late_cancel_hours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 720
                  },
                  "no_show_fee_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000000
                  },
                  "hold_ttl_minutes": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "reminder_lead_minutes": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "deposit_messages_enabled": {
                    "type": "boolean"
                  },
                  "deposit_reminder_lead_minutes": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1440
                  },
                  "sales_team_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "next_day_cutoff_local": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                  },
                  "next_day_earliest_local": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                  }
                }
              },
              "example": {
                "timezone": "America/Santiago",
                "business_hours": {
                  "mon": [
                    [
                      "09:00",
                      "13:00"
                    ],
                    [
                      "14:30",
                      "18:00"
                    ]
                  ],
                  "fri": [
                    [
                      "09:00",
                      "13:00"
                    ],
                    [
                      "14:30",
                      "17:00"
                    ]
                  ]
                },
                "slot_minutes": 30,
                "buffer_minutes": 10,
                "staff_capacity": 2,
                "reminder_lead_minutes": 120
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The effective config (same shape as GET)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "timezone": "America/Santiago",
                    "business_hours": {
                      "mon": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "tue": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "wed": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "thu": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "fri": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "17:00"
                        ]
                      ]
                    },
                    "effective_business_hours": {
                      "mon": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "tue": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "wed": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "thu": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "18:00"
                        ]
                      ],
                      "fri": [
                        [
                          "09:00",
                          "13:00"
                        ],
                        [
                          "14:30",
                          "17:00"
                        ]
                      ]
                    },
                    "business_hours_source": "config",
                    "holidays": [
                      "2026-09-18",
                      "2026-09-19",
                      "2026-10-12",
                      "2026-10-31"
                    ],
                    "slot_minutes": 30,
                    "buffer_minutes": 10,
                    "min_lead_minutes": 120,
                    "booking_horizon_days": 14,
                    "staff_capacity": 2,
                    "vehicle_exclusive": false,
                    "owner_capacity": 1,
                    "hold_ttl_minutes": 15,
                    "reminder_lead_minutes": 120,
                    "deposit_messages_enabled": true,
                    "deposit_reminder_lead_minutes": 120,
                    "sales_team_id": "cccccccc-0000-4000-8000-000000000001",
                    "next_day_cutoff_local": "18:00",
                    "next_day_earliest_local": "11:00",
                    "arrival_board_enabled": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/calendar": {
      "get": {
        "summary": "Appointments in a calendar window",
        "description": "Everything OVERLAPPING [from, to) — an appointment that starts before `from` and ends after it IS on screen and IS returned, which is what separates this from the `starts_at`-filtered list. Ascending, with contact / vehicle / type / owner / location display names joined so a month view is one request. Range capped at 62 days. `mine=true` narrows to the caller's own diary (owned by me OR unassigned).\n\n**Abono / depósito.** A booking that opened a deposit expectation carries an `abono` object; one that did not carries `abono: null`. It is derived from the cita’s `customer_obligation` of kind `appointment_deposit` and its allocations, so it holds `amount_clp` (what is owed), `paid_clp` (what has landed), `outstanding_clp`, `deadline` (`due_on`, a calendar day), the derived `state` (`pending` | `partial` | `paid` | `overpaid` | `overdue` | `overdue_partial`), the badge’s `effective_status` and `provenance` (`verified` = a bank said so, `manual` = a human did).\n\n`overdue` is **derived, never stored**: it is an open obligation whose `due_on` has passed with something still outstanding. There is no cron and no transition behind it — a late transfer still settles the obligation. The full picture for a person, including their credit, is `GET /contacts/{id}/ledger`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "test_drive",
                  "external",
                  "block",
                  "clinic"
                ]
              }
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pending_hold",
                  "confirmed",
                  "cancelled",
                  "completed",
                  "no_show"
                ]
              }
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "owner_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "property_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean"
            },
            "required": false,
            "name": "mine",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Calendar appointments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "display_id": "A-3",
                      "kind": "external",
                      "status": "confirmed",
                      "engine": "native",
                      "title": null,
                      "source": "human",
                      "starts_at": "2026-10-01T14:00:00.000Z",
                      "ends_at": "2026-10-01T14:45:00.000Z",
                      "hold_expires_at": null,
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "Camila R.",
                        "phone": "+56900000001",
                        "email": null
                      },
                      "customer_name": "Camila R.",
                      "vehicle": null,
                      "appointment_type": {
                        "id": "eeee0000-0000-4000-8000-000000000001",
                        "name": "Consulta de evaluación"
                      },
                      "owner": {
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Valentina S."
                      },
                      "location": {
                        "id": "b1b1b1b1-0000-4000-8000-000000000001",
                        "name": "Sucursal Centro"
                      },
                      "conversation_id": null,
                      "lead_id": null,
                      "notes": "Primera visita.",
                      "metadata": {},
                      "abono": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments": {
      "get": {
        "summary": "List appointments",
        "description": "Cursor-paginated: read `pagination.nextCursor` from the envelope and send it back as `cursor`. A null `nextCursor` means the end. There is no total and no offset.\n\n`status` and `kind` accept comma-separated lists, so `?status=confirmed,pending_hold` is one filter rather than two calls. Everything else (`vehicle_id`, `appointment_type_id`, `owner_user_id`, `lead_id`, `contact_id`, `from`, `to`) is a single value and the filters AND together.\n\nNothing is excluded by default — cancelled appointments and `block` rows come back alongside real visits. A calendar view almost always wants `status=pending_hold,confirmed` and to treat `kind=block` as shading rather than as a booking.\n\n**Row visibility is forced from the principal**, so a restricted role sees only the appointments it is allowed to see. `owner_user_id` is a UI filter ANDed with that ceiling, never a way around it — asking for a colleague's diary narrows the caller's own visible rows rather than widening them.\n\n**No `abono_*` columns (T12).** This flat list carries appointment facts only: a cita’s deposit is a `customer_obligation` and the badge rides the joined `/appointments/calendar` shape under `abono`. The whole money picture for a person is `GET /contacts/{id}/ledger`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pending_hold",
                  "confirmed",
                  "cancelled",
                  "completed",
                  "no_show"
                ]
              }
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "test_drive",
                  "external",
                  "block",
                  "clinic"
                ]
              }
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "property_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "appointment_type_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "owner_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of appointments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "conversation_id": null,
                      "vehicle_id": null,
                      "appointment_type_id": "eeee0000-0000-4000-8000-000000000001",
                      "owner_user_id": "11111111-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "professional_id": null,
                      "resource_id": null,
                      "kind": "external",
                      "source": "human",
                      "engine": "native",
                      "external_id": null,
                      "status": "confirmed",
                      "status_epoch": 1,
                      "clinic_status_id": null,
                      "arrival_state": "scheduled",
                      "arrived_at": null,
                      "in_room_at": null,
                      "done_at": null,
                      "series_id": null,
                      "series_index": null,
                      "booking_landing_id": null,
                      "attribution": null,
                      "starts_at": "2026-10-01T14:00:00.000Z",
                      "ends_at": "2026-10-01T14:45:00.000Z",
                      "hold_expires_at": null,
                      "google_event_id": null,
                      "google_ical_uid": null,
                      "google_updated_at": null,
                      "google_etag": null,
                      "calendar_mirror_status": null,
                      "calendar_mirror_op": null,
                      "calendar_mirror_error": null,
                      "calendar_mirror_attempts": 0,
                      "calendar_mirror_attempted_at": null,
                      "calendar_mirror_next_retry_at": null,
                      "calendar_mirror_calendar_id": null,
                      "reminder_at": "2026-10-01T12:00:00.000Z",
                      "reminder_sent_at": null,
                      "reminder_job_id": "appt-reminder-dddddddd-0000-4000-8000-000000000001",
                      "customer_name": "Camila R.",
                      "notes": "Primera visita.",
                      "metadata": {},
                      "display_seq": 3,
                      "display_id": "A-3",
                      "created_at": "2026-09-22T12:50:37.046Z",
                      "updated_at": "2026-09-22T12:50:37.080Z"
                    },
                    {
                      "id": "dddddddd-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "lead_id": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "conversation_id": null,
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "appointment_type_id": "eeee0000-0000-4000-8000-000000000002",
                      "owner_user_id": "11111111-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "professional_id": null,
                      "resource_id": null,
                      "kind": "test_drive",
                      "source": "human",
                      "engine": "native",
                      "external_id": null,
                      "status": "confirmed",
                      "status_epoch": 1,
                      "clinic_status_id": null,
                      "arrival_state": "scheduled",
                      "arrived_at": null,
                      "in_room_at": null,
                      "done_at": null,
                      "series_id": null,
                      "series_index": null,
                      "booking_landing_id": null,
                      "attribution": null,
                      "starts_at": "2026-10-02T13:00:00.000Z",
                      "ends_at": "2026-10-02T13:30:00.000Z",
                      "hold_expires_at": null,
                      "google_event_id": null,
                      "google_ical_uid": null,
                      "google_updated_at": null,
                      "google_etag": null,
                      "calendar_mirror_status": null,
                      "calendar_mirror_op": null,
                      "calendar_mirror_error": null,
                      "calendar_mirror_attempts": 0,
                      "calendar_mirror_attempted_at": null,
                      "calendar_mirror_next_retry_at": null,
                      "calendar_mirror_calendar_id": null,
                      "reminder_at": "2026-10-02T11:00:00.000Z",
                      "reminder_sent_at": null,
                      "reminder_job_id": "appt-reminder-dddddddd-0000-4000-8000-000000000002",
                      "customer_name": "Diego M.",
                      "notes": null,
                      "metadata": {},
                      "display_seq": 4,
                      "display_id": "A-4",
                      "created_at": "2026-09-22T12:50:37.046Z",
                      "updated_at": "2026-09-22T12:50:37.080Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "nextCursor": "eyJ0cyI6IjIwMjYtMTAtMDJUMTM6MDA6MDAuMDAwWiIsImlkIjoiZGRkZGRkZGQtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAyIn0",
                      "limit": 25
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Book an appointment",
        "description": "Books and confirms in one step — there is no separate confirm call on this surface, so a 201 is a confirmed appointment, not a hold. It also schedules the customer reminder, mirrors the event to Google and invites the executive (both best-effort), and fires `appointment.booked`.\n\n**Booked as an operator, so the opening-hours and lead-time gates do not apply.** A REST caller may book at 3am, ten minutes from now, or in the past; only the agent is held to the business-hours window. Physical conflicts are still enforced under a per-tenant lock, so two simultaneous requests for the last slot cannot both win.\n\nEvery refusal carries a machine-readable `details.reason`, and the STATUS splits on what that reason describes. A clash with the world is a **409**: `slot_taken` (the WINDOW's overall staff capacity is full, checked when the booking names no `owner_user_id`), `professional_taken` (the ONE named `owner_user_id` is already at their own per-slot capacity — distinct from `slot_taken` because a workspace can staff the window while that one person cannot take another), `vehicle_taken` (that car is already out on another visit), `property_taken` (an exclusive visit type and another buyer already visits that property in the window) or `blocked` (an admin block covers it). All five are losable races, so the recovery is to offer another slot. A problem with the request is a **400**: `invalid` (the times do not parse, the end is not after the start, or the appointment type is unknown or inactive). Branch on `details.reason`, never on the message text.\n\nA workspace with no schedule config is **no longer refused**: it books on the table’s default knobs and the workspace’s own opening hours, the same policy `GET /appointments/config` reports with `configured: false`. The `not_configured` reason is therefore no longer emitted by this surface.\n\n`kind` defaults to `test_drive`. Use `block` to mark a window unbookable: it skips capacity entirely, takes no executive, and is created already confirmed.\n\n`owner_user_id` pins a specific member; leave it out and one is assigned round-robin from the appointment type’s eligible staff. Omitting `appointment_type_id` on a `test_drive` resolves the workspace default type.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "test_drive",
                      "external",
                      "block",
                      "clinic"
                    ]
                  },
                  "vehicle_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "property_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "project_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_type_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "customer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "starts_at",
                  "ends_at"
                ]
              },
              "example": {
                "starts_at": "2026-10-01T14:00:00.000Z",
                "ends_at": "2026-10-01T14:45:00.000Z",
                "kind": "external",
                "appointment_type_id": "eeee0000-0000-4000-8000-000000000001",
                "contact_id": "22222222-0000-4000-8000-000000000001",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                "customer_name": "Camila R.",
                "notes": "Primera visita."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The confirmed appointment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "conversation_id": null,
                    "vehicle_id": null,
                    "appointment_type_id": "eeee0000-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "professional_id": null,
                    "resource_id": null,
                    "kind": "external",
                    "source": "human",
                    "engine": "native",
                    "external_id": null,
                    "status": "confirmed",
                    "status_epoch": 1,
                    "clinic_status_id": null,
                    "arrival_state": "scheduled",
                    "arrived_at": null,
                    "in_room_at": null,
                    "done_at": null,
                    "series_id": null,
                    "series_index": null,
                    "booking_landing_id": null,
                    "attribution": null,
                    "starts_at": "2026-10-01T14:00:00.000Z",
                    "ends_at": "2026-10-01T14:45:00.000Z",
                    "hold_expires_at": null,
                    "google_event_id": null,
                    "google_ical_uid": null,
                    "google_updated_at": null,
                    "google_etag": null,
                    "calendar_mirror_status": null,
                    "calendar_mirror_op": null,
                    "calendar_mirror_error": null,
                    "calendar_mirror_attempts": 0,
                    "calendar_mirror_attempted_at": null,
                    "calendar_mirror_next_retry_at": null,
                    "calendar_mirror_calendar_id": null,
                    "reminder_at": "2026-10-01T12:00:00.000Z",
                    "reminder_sent_at": null,
                    "reminder_job_id": "appt-reminder-dddddddd-0000-4000-8000-000000000001",
                    "customer_name": "Camila R.",
                    "notes": "Primera visita.",
                    "metadata": {},
                    "display_seq": 3,
                    "display_id": "A-3",
                    "created_at": "2026-09-22T12:50:37.046Z",
                    "updated_at": "2026-09-22T12:50:37.080Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/appointments/{id}": {
      "get": {
        "summary": "Fetch one appointment",
        "description": "By UUID or by `A-` display id — both resolve here, so an id copied from the operator UI works directly.\n\nAppointment facts only — no `abono_*` columns (T12). The deposit badge rides `/appointments/calendar`; the money picture is `GET /contacts/{id}/ledger`.\n\n`attribution` is the ad click of the visit that booked this cita on a public booking surface (`utm_*`, `fbclid` / `gclid`, the tag's `anonymous_id`, `surface`, `captured_at`): what its booking outcome carried to the attribution engine. `null` for staff, agent and mirrored bookings.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The appointment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "conversation_id": null,
                    "vehicle_id": null,
                    "appointment_type_id": "eeee0000-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "professional_id": null,
                    "resource_id": null,
                    "kind": "external",
                    "source": "human",
                    "engine": "native",
                    "external_id": null,
                    "status": "confirmed",
                    "status_epoch": 1,
                    "clinic_status_id": null,
                    "arrival_state": "scheduled",
                    "arrived_at": null,
                    "in_room_at": null,
                    "done_at": null,
                    "series_id": null,
                    "series_index": null,
                    "booking_landing_id": null,
                    "attribution": null,
                    "starts_at": "2026-10-01T14:00:00.000Z",
                    "ends_at": "2026-10-01T14:45:00.000Z",
                    "hold_expires_at": null,
                    "google_event_id": null,
                    "google_ical_uid": null,
                    "google_updated_at": null,
                    "google_etag": null,
                    "calendar_mirror_status": null,
                    "calendar_mirror_op": null,
                    "calendar_mirror_error": null,
                    "calendar_mirror_attempts": 0,
                    "calendar_mirror_attempted_at": null,
                    "calendar_mirror_next_retry_at": null,
                    "calendar_mirror_calendar_id": null,
                    "reminder_at": "2026-10-01T12:00:00.000Z",
                    "reminder_sent_at": null,
                    "reminder_job_id": "appt-reminder-dddddddd-0000-4000-8000-000000000001",
                    "customer_name": "Camila R.",
                    "notes": "Primera visita.",
                    "metadata": {},
                    "display_seq": 3,
                    "display_id": "A-3",
                    "created_at": "2026-09-22T12:50:37.046Z",
                    "updated_at": "2026-09-22T12:50:37.080Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Reschedule, reassign or close an appointment",
        "description": "Four distinct operations behind one verb, applied in a fixed order: reschedule, then status, then reassign, then move sucursal. Sending several at once is allowed and they are NOT atomic — a later step failing leaves the earlier ones applied.\n\n**Reschedule** needs `starts_at` and `ends_at` together; one without the other is a 400. It re-checks capacity, buffer, blocks and the vehicle against the NEW window, ignoring the appointment being moved, and frees the old slot on success. Every refusal carries a machine-readable `details.reason`, and the STATUS splits on what that reason describes. A clash with the world is a **409**: `slot_taken` (the WINDOW's overall staff capacity is full, checked when the booking names no `owner_user_id`), `professional_taken` (the ONE named `owner_user_id` is already at their own per-slot capacity — distinct from `slot_taken` because a workspace can staff the window while that one person cannot take another), `vehicle_taken` (that car is already out on another visit), `property_taken` (an exclusive visit type and another buyer already visits that property in the window) or `blocked` (an admin block covers it). All five are losable races, so the recovery is to offer another slot. A problem with the request is a **400**: `invalid` (the times do not parse, the end is not after the start, or the appointment type is unknown or inactive). Branch on `details.reason`, never on the message text.\n\nA workspace with no schedule config is **no longer refused**: it books on the table’s default knobs and the workspace’s own opening hours, the same policy `GET /appointments/config` reports with `configured: false`. The `not_configured` reason is therefore no longer emitted by this surface.\n\n**Status** accepts `completed` and `no_show`, and nothing else. Each fires its own Evento (`appointment.completed` / `appointment.no_show`); neither changes the calendar and neither frees the slot, because the time was used. **`cancelled` is deliberately not reachable here** — the Zod enum rejects it with a 400. Cancelling frees the slot, drops the calendar event and messages the customer, so it has one door of its own: `POST /appointments/{id}/cancel`, under `appointments:delete` plus `messages:send`. That split lets a role be granted \"may book and reschedule\" without also being granted \"may un-book\".\n\n**Reassign** via `owner_user_id` moves the appointment to another executive, or to nobody with `null`. It changes the row only — the Google invite is not re-sent, so the new owner does not receive a calendar invitation from this call.\n\n**`location_id`** moves the visit between sucursales, and `null` clears it. A location that is not this workspace’s is a 404.\n\nOnly the reschedule step reports failure. A status or reassign write that does not apply answers 200 with the appointment unchanged, so compare the response rather than assuming.\n\nThe response is the JOINED row — the same shape `/appointments/calendar` returns, with the contact / vehicle / type / owner / location names resolved — not the bare row, so a detail view can re-render straight from it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "owner_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "completed",
                      "no_show"
                    ]
                  }
                }
              },
              "example": {
                "starts_at": "2026-10-02T16:00:00.000Z",
                "ends_at": "2026-10-02T16:45:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The appointment after the applied changes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "display_id": "A-3",
                    "kind": "external",
                    "status": "confirmed",
                    "engine": "native",
                    "title": null,
                    "source": "human",
                    "starts_at": "2026-10-02T16:00:00.000Z",
                    "ends_at": "2026-10-02T16:45:00.000Z",
                    "hold_expires_at": null,
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "Camila R.",
                      "phone": "+56900000001",
                      "email": null
                    },
                    "customer_name": "Camila R.",
                    "vehicle": null,
                    "appointment_type": {
                      "id": "eeee0000-0000-4000-8000-000000000001",
                      "name": "Consulta de evaluación"
                    },
                    "owner": {
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": "Valentina S."
                    },
                    "location": {
                      "id": "b1b1b1b1-0000-4000-8000-000000000001",
                      "name": "Sucursal Centro"
                    },
                    "conversation_id": null,
                    "lead_id": null,
                    "notes": "Primera visita.",
                    "metadata": {},
                    "abono": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/{id}/cancel": {
      "post": {
        "summary": "Cancel an appointment",
        "description": "Sets `cancelled`, drops the pending reminder, removes the mirrored calendar event, fires `appointment.cancelled` and frees the slot for the follow-up trigger. Idempotent — cancelling an already-cancelled appointment answers 200 with the row and does none of it again.\n\n**Two scopes: `appointments:delete` AND `messages:send`.** The first is the split that lets a workspace grant \"may book and reschedule\" without granting \"may un-book\" (`appointments:write` reaches neither). The second is because a MESSAGE LEAVES VITRINA here, which is what every sending operation costs (ADR 0106 §3.2) — a credential that may cancel but holds no authority to write to anybody gets a 403.\n\n**It messages the customer.** The workspace is the one calling this off, so the cancellation notice goes out on the appointment’s originating conversation, in the workspace language and timezone. That send is best-effort and stamped for idempotency — it never fails the call and never goes out twice — and it is skipped entirely when the appointment has no conversation behind it. It is a transactional confirmation Vitrina composes about a booking the person made, not a send this caller authors, so it does not go through the política de envíos verdicts: a Bloqueo there would leave somebody turning up to a cancelled appointment.\n\n**`reason` is not stored, but it IS shown**: the text is appended to that customer notice, and nowhere else. Nothing persists it on the appointment, so put the internal explanation in a note on the lead or conversation if it needs to survive — and write `reason` as something the customer may read.\n\n**`postpone: true`** is the same cancellation for a customer who cannot make it with no new date yet: everything above happens, and the appointment is ALSO recorded as postponed — `metadata.postponed` = `{ at, reason, by }`, the one place `reason` is stored. The lead stays open (a cancelled visit steps it back to qualified, never to lost).\n\nAn appointment imported FROM Google is not deleted upstream — the calendar owns those, so the cancellation is local and the event stays on the customer’s calendar.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  },
                  "postpone": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "reason": "Reprogramamos por disponibilidad del equipo."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled appointment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "conversation_id": null,
                    "vehicle_id": null,
                    "appointment_type_id": "eeee0000-0000-4000-8000-000000000001",
                    "owner_user_id": "11111111-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "professional_id": null,
                    "resource_id": null,
                    "kind": "external",
                    "source": "human",
                    "engine": "native",
                    "external_id": null,
                    "status": "cancelled",
                    "status_epoch": 2,
                    "clinic_status_id": null,
                    "arrival_state": "scheduled",
                    "arrived_at": null,
                    "in_room_at": null,
                    "done_at": null,
                    "series_id": null,
                    "series_index": null,
                    "booking_landing_id": null,
                    "attribution": null,
                    "starts_at": "2026-10-01T14:00:00.000Z",
                    "ends_at": "2026-10-01T14:45:00.000Z",
                    "hold_expires_at": null,
                    "google_event_id": null,
                    "google_ical_uid": null,
                    "google_updated_at": null,
                    "google_etag": null,
                    "calendar_mirror_status": null,
                    "calendar_mirror_op": null,
                    "calendar_mirror_error": null,
                    "calendar_mirror_attempts": 0,
                    "calendar_mirror_attempted_at": null,
                    "calendar_mirror_next_retry_at": null,
                    "calendar_mirror_calendar_id": null,
                    "reminder_at": "2026-10-01T12:00:00.000Z",
                    "reminder_sent_at": null,
                    "reminder_job_id": "appt-reminder-dddddddd-0000-4000-8000-000000000001",
                    "customer_name": "Camila R.",
                    "notes": "Primera visita.",
                    "metadata": {},
                    "display_seq": 3,
                    "display_id": "A-3",
                    "created_at": "2026-09-22T12:50:37.046Z",
                    "updated_at": "2026-09-22T12:50:37.080Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/appointments/reminders/plan": {
      "get": {
        "summary": "Get the appointment reminder ladder",
        "description": "The rungs, the single-sender declaration, whether anything is being sent right now (`live`) and `blocked_reason` when it is not. A rung has an `event` (`before_start` by default, `booked` for the welcome right after booking, `no_show` for the rebook with three fresh slots), an `email` mode (`never` / `fallback` / `also`, the twin with a .ics) and optional per-kind WhatsApp `templates` (`demo`, `clinic`, `visit`, `generic`). Off for every tenant until its owner saves a plan and declares Vitrina the single sender.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "responses": {
          "200": {
            "description": "Reminder plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "rungs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "single_sender_confirmed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "single_sender_confirmed_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vendor_sends_reminders": {
                          "type": "boolean"
                        },
                        "live": {
                          "type": "boolean"
                        },
                        "blocked_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "no_plan",
                            "inactive",
                            "single_sender_not_confirmed",
                            "vendor_sends_reminders",
                            "no_rungs"
                          ]
                        },
                        "defaults": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "confirm_status_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "confirm_status": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            },
                            "external_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "heuristic"
                              ]
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "external_id",
                            "source"
                          ]
                        },
                        "create_contacts_from_patient_phone": {
                          "type": "boolean"
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "accounts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "phone_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "display": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "phone_number",
                              "display",
                              "enabled"
                            ]
                          }
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rung_key": {
                                "type": "string"
                              },
                              "code": {
                                "type": "string",
                                "enum": [
                                  "no_template_outside_window",
                                  "template_missing",
                                  "template_not_approved",
                                  "template_positional_variables",
                                  "template_unknown_variable",
                                  "template_unsupported",
                                  "account_not_chosen"
                                ]
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "rung_key",
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "rungs",
                        "active",
                        "single_sender_confirmed_at",
                        "single_sender_confirmed_by",
                        "vendor_sends_reminders",
                        "live",
                        "blocked_reason",
                        "defaults",
                        "updated_at",
                        "confirm_status_id",
                        "confirm_status",
                        "create_contacts_from_patient_phone",
                        "messaging_account_id",
                        "accounts",
                        "warnings"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
                    "name": "Recordatorios de cita",
                    "rungs": [
                      {
                        "key": "welcome",
                        "event": "booked",
                        "offset_hours": 0,
                        "channel": "whatsapp",
                        "email": "also",
                        "template_name": null,
                        "templates": {
                          "demo": "demo_confirmada_v1"
                        },
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "h24",
                        "offset_hours": 24,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "recordatorio_24h",
                        "asks_confirmation": true,
                        "template": {
                          "name": "recordatorio_24h",
                          "language": "es",
                          "status": "APPROVED",
                          "category": "UTILITY",
                          "body_preview": "Hola {{nombre}}, te recordamos tu cita del {{fecha}} a las {{hora}}. ¿Nos confirmas?",
                          "variables": [
                            "nombre",
                            "fecha",
                            "hora"
                          ],
                          "buttons": [
                            {
                              "type": "QUICK_REPLY",
                              "text": "Confirmo"
                            },
                            {
                              "type": "QUICK_REPLY",
                              "text": "No puedo asistir"
                            }
                          ],
                          "usable": true,
                          "reasons": []
                        },
                        "derived": {
                          "asks_confirmation": true,
                          "includes_link": false
                        }
                      },
                      {
                        "key": "h2",
                        "offset_hours": 2,
                        "channel": "whatsapp",
                        "template_name": "recordatorio_2h",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "no_show",
                        "event": "no_show",
                        "offset_hours": 0.17,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "reagendar",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      }
                    ],
                    "active": true,
                    "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
                    "single_sender_confirmed_by": null,
                    "vendor_sends_reminders": false,
                    "live": true,
                    "blocked_reason": null,
                    "defaults": [],
                    "updated_at": "2026-10-11T12:00:00.000Z",
                    "confirm_status_id": null,
                    "confirm_status": null,
                    "create_contacts_from_patient_phone": true,
                    "messaging_account_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                    "accounts": [
                      {
                        "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                        "name": "Recepción",
                        "phone_number": "+56922223333",
                        "display": "Recepción · +56922223333",
                        "enabled": true
                      }
                    ],
                    "warnings": [
                      {
                        "rung_key": "welcome",
                        "code": "template_missing",
                        "message": "Plantilla (demo): no existe una plantilla «demo_confirmada_v1» en este número de WhatsApp; fuera de la ventana de 24 h no saldrá nada."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Save the appointment reminder ladder",
        "description": "Replaces the rungs. 400 `single_sender_not_confirmed` when `active` is requested without the declaration. `messaging_account_id` picks the sending WhatsApp number (400 `reminder_account_required` when there are several and none is chosen, `reminder_account_not_found` / `reminder_account_disabled`). Outside the 24 h WhatsApp window a rung needs an APPROVED template on that number; `template_name` is checked on save (400 `template_not_found` / `template_not_approved` / `template_positional_variables` / `template_unknown_variable` / `template_unsupported`). Named variables: `nombre`, `fecha`, `hora`, `profesional`, `sucursal`, `tratamiento`, `enlace`, `anfitrion`, `reunion`, `negocio`, `opcion_1`..`opcion_3`, `opciones`. A templated rung's `asks_confirmation` is derived from its quick replies and the manage link rides iff it uses `{{enlace}}`; with `email: \"fallback\"` a missing template falls back to email instead of staying silent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "rungs": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "pattern": "^[a-z0-9_]{1,32}$"
                        },
                        "offset_hours": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 720
                        },
                        "event": {
                          "type": "string",
                          "enum": [
                            "before_start",
                            "booked",
                            "no_show",
                            "host_changed"
                          ]
                        },
                        "email": {
                          "type": "string",
                          "enum": [
                            "never",
                            "fallback",
                            "also"
                          ]
                        },
                        "templates": {
                          "type": "object",
                          "properties": {
                            "demo": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 512
                            },
                            "clinic": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 512
                            },
                            "visit": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 512
                            },
                            "generic": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 512
                            }
                          }
                        },
                        "channel": {
                          "type": "string",
                          "enum": [
                            "whatsapp",
                            "email"
                          ],
                          "default": "whatsapp"
                        },
                        "template_name": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "minLength": 1,
                          "maxLength": 512
                        },
                        "asks_confirmation": {
                          "type": "boolean",
                          "default": false
                        },
                        "template": {},
                        "derived": {}
                      },
                      "required": [
                        "key",
                        "offset_hours"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 6
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "messaging_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "vendor_sends_reminders": {
                    "type": "boolean"
                  },
                  "confirm_status_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "create_contacts_from_patient_phone": {
                    "type": "boolean"
                  },
                  "quiet_hours_enforced": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "rungs",
                  "active"
                ]
              },
              "example": {
                "rungs": [
                  {
                    "key": "welcome",
                    "event": "booked",
                    "offset_hours": 0,
                    "channel": "whatsapp",
                    "email": "also",
                    "template_name": null,
                    "templates": {
                      "demo": "demo_confirmada_v1"
                    },
                    "asks_confirmation": false
                  },
                  {
                    "key": "h24",
                    "offset_hours": 24,
                    "channel": "whatsapp",
                    "email": "fallback",
                    "template_name": "recordatorio_24h",
                    "asks_confirmation": true
                  },
                  {
                    "key": "h2",
                    "offset_hours": 2,
                    "channel": "whatsapp",
                    "template_name": "recordatorio_2h",
                    "asks_confirmation": false
                  },
                  {
                    "key": "no_show",
                    "event": "no_show",
                    "offset_hours": 0.17,
                    "channel": "whatsapp",
                    "email": "fallback",
                    "template_name": "reagendar",
                    "asks_confirmation": false
                  }
                ],
                "active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reminder plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "rungs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "single_sender_confirmed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "single_sender_confirmed_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vendor_sends_reminders": {
                          "type": "boolean"
                        },
                        "live": {
                          "type": "boolean"
                        },
                        "blocked_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "no_plan",
                            "inactive",
                            "single_sender_not_confirmed",
                            "vendor_sends_reminders",
                            "no_rungs"
                          ]
                        },
                        "defaults": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "confirm_status_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "confirm_status": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            },
                            "external_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "heuristic"
                              ]
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "external_id",
                            "source"
                          ]
                        },
                        "create_contacts_from_patient_phone": {
                          "type": "boolean"
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "accounts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "phone_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "display": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "phone_number",
                              "display",
                              "enabled"
                            ]
                          }
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rung_key": {
                                "type": "string"
                              },
                              "code": {
                                "type": "string",
                                "enum": [
                                  "no_template_outside_window",
                                  "template_missing",
                                  "template_not_approved",
                                  "template_positional_variables",
                                  "template_unknown_variable",
                                  "template_unsupported",
                                  "account_not_chosen"
                                ]
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "rung_key",
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "rungs",
                        "active",
                        "single_sender_confirmed_at",
                        "single_sender_confirmed_by",
                        "vendor_sends_reminders",
                        "live",
                        "blocked_reason",
                        "defaults",
                        "updated_at",
                        "confirm_status_id",
                        "confirm_status",
                        "create_contacts_from_patient_phone",
                        "messaging_account_id",
                        "accounts",
                        "warnings"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
                    "name": "Recordatorios de cita",
                    "rungs": [
                      {
                        "key": "welcome",
                        "event": "booked",
                        "offset_hours": 0,
                        "channel": "whatsapp",
                        "email": "also",
                        "template_name": null,
                        "templates": {
                          "demo": "demo_confirmada_v1"
                        },
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "h24",
                        "offset_hours": 24,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "recordatorio_24h",
                        "asks_confirmation": true,
                        "template": {
                          "name": "recordatorio_24h",
                          "language": "es",
                          "status": "APPROVED",
                          "category": "UTILITY",
                          "body_preview": "Hola {{nombre}}, te recordamos tu cita del {{fecha}} a las {{hora}}. ¿Nos confirmas?",
                          "variables": [
                            "nombre",
                            "fecha",
                            "hora"
                          ],
                          "buttons": [
                            {
                              "type": "QUICK_REPLY",
                              "text": "Confirmo"
                            },
                            {
                              "type": "QUICK_REPLY",
                              "text": "No puedo asistir"
                            }
                          ],
                          "usable": true,
                          "reasons": []
                        },
                        "derived": {
                          "asks_confirmation": true,
                          "includes_link": false
                        }
                      },
                      {
                        "key": "h2",
                        "offset_hours": 2,
                        "channel": "whatsapp",
                        "template_name": "recordatorio_2h",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "no_show",
                        "event": "no_show",
                        "offset_hours": 0.17,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "reagendar",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      }
                    ],
                    "active": true,
                    "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
                    "single_sender_confirmed_by": null,
                    "vendor_sends_reminders": false,
                    "live": true,
                    "blocked_reason": null,
                    "defaults": [],
                    "updated_at": "2026-10-11T12:00:00.000Z",
                    "confirm_status_id": null,
                    "confirm_status": null,
                    "create_contacts_from_patient_phone": true,
                    "messaging_account_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                    "accounts": [
                      {
                        "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                        "name": "Recepción",
                        "phone_number": "+56922223333",
                        "display": "Recepción · +56922223333",
                        "enabled": true
                      }
                    ],
                    "warnings": [
                      {
                        "rung_key": "welcome",
                        "code": "template_missing",
                        "message": "Plantilla (demo): no existe una plantilla «demo_confirmada_v1» en este número de WhatsApp; fuera de la ventana de 24 h no saldrá nada."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/reminders/templates": {
      "get": {
        "summary": "List the WhatsApp templates a reminder rung can use",
        "description": "Every template on the WhatsApp account (`messaging_account_id`, default: the plan's sending account), any status, each with `usable` and the `reasons` it is not (`template_not_approved`, `template_positional_variables`, `template_unknown_variable`, `template_unsupported`). Usable first.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "messaging_account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Reminder templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": "string"
                          },
                          "language": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "category": {
                            "type": "string"
                          },
                          "body_preview": {
                            "type": "string"
                          },
                          "variables": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "buttons": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "type": {
                                  "type": "string",
                                  "enum": [
                                    "QUICK_REPLY",
                                    "URL",
                                    "PHONE_NUMBER",
                                    "FLOW"
                                  ]
                                },
                                "text": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "type",
                                "text"
                              ]
                            }
                          },
                          "usable": {
                            "type": "boolean"
                          },
                          "reasons": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "enum": [
                                "template_not_approved",
                                "template_positional_variables",
                                "template_unknown_variable",
                                "template_unsupported"
                              ]
                            }
                          }
                        },
                        "required": [
                          "name",
                          "language",
                          "status",
                          "category",
                          "body_preview",
                          "variables",
                          "buttons",
                          "usable",
                          "reasons"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "name": "recordatorio_24h",
                      "language": "es",
                      "status": "APPROVED",
                      "category": "UTILITY",
                      "body_preview": "Hola {{nombre}}, te recordamos tu cita del {{fecha}} a las {{hora}}. ¿Nos confirmas?",
                      "variables": [
                        "nombre",
                        "fecha",
                        "hora"
                      ],
                      "buttons": [
                        {
                          "type": "QUICK_REPLY",
                          "text": "Confirmo"
                        },
                        {
                          "type": "QUICK_REPLY",
                          "text": "No puedo asistir"
                        }
                      ],
                      "usable": true,
                      "reasons": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointments/reminders/plan/confirm-single-sender": {
      "post": {
        "summary": "Declare Vitrina the single sender of reminders",
        "description": "Records who declared that no other system reminds these people (the only thing that makes an active ladder legal). Reversible with POST /appointments/reminders/plan/revoke-single-sender.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "responses": {
          "200": {
            "description": "Reminder plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "rungs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "single_sender_confirmed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "single_sender_confirmed_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vendor_sends_reminders": {
                          "type": "boolean"
                        },
                        "live": {
                          "type": "boolean"
                        },
                        "blocked_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "no_plan",
                            "inactive",
                            "single_sender_not_confirmed",
                            "vendor_sends_reminders",
                            "no_rungs"
                          ]
                        },
                        "defaults": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "confirm_status_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "confirm_status": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            },
                            "external_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "heuristic"
                              ]
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "external_id",
                            "source"
                          ]
                        },
                        "create_contacts_from_patient_phone": {
                          "type": "boolean"
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "accounts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "phone_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "display": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "phone_number",
                              "display",
                              "enabled"
                            ]
                          }
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rung_key": {
                                "type": "string"
                              },
                              "code": {
                                "type": "string",
                                "enum": [
                                  "no_template_outside_window",
                                  "template_missing",
                                  "template_not_approved",
                                  "template_positional_variables",
                                  "template_unknown_variable",
                                  "template_unsupported",
                                  "account_not_chosen"
                                ]
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "rung_key",
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "rungs",
                        "active",
                        "single_sender_confirmed_at",
                        "single_sender_confirmed_by",
                        "vendor_sends_reminders",
                        "live",
                        "blocked_reason",
                        "defaults",
                        "updated_at",
                        "confirm_status_id",
                        "confirm_status",
                        "create_contacts_from_patient_phone",
                        "messaging_account_id",
                        "accounts",
                        "warnings"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
                    "name": "Recordatorios de cita",
                    "rungs": [
                      {
                        "key": "welcome",
                        "event": "booked",
                        "offset_hours": 0,
                        "channel": "whatsapp",
                        "email": "also",
                        "template_name": null,
                        "templates": {
                          "demo": "demo_confirmada_v1"
                        },
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "h24",
                        "offset_hours": 24,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "recordatorio_24h",
                        "asks_confirmation": true,
                        "template": {
                          "name": "recordatorio_24h",
                          "language": "es",
                          "status": "APPROVED",
                          "category": "UTILITY",
                          "body_preview": "Hola {{nombre}}, te recordamos tu cita del {{fecha}} a las {{hora}}. ¿Nos confirmas?",
                          "variables": [
                            "nombre",
                            "fecha",
                            "hora"
                          ],
                          "buttons": [
                            {
                              "type": "QUICK_REPLY",
                              "text": "Confirmo"
                            },
                            {
                              "type": "QUICK_REPLY",
                              "text": "No puedo asistir"
                            }
                          ],
                          "usable": true,
                          "reasons": []
                        },
                        "derived": {
                          "asks_confirmation": true,
                          "includes_link": false
                        }
                      },
                      {
                        "key": "h2",
                        "offset_hours": 2,
                        "channel": "whatsapp",
                        "template_name": "recordatorio_2h",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "no_show",
                        "event": "no_show",
                        "offset_hours": 0.17,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "reagendar",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      }
                    ],
                    "active": true,
                    "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
                    "single_sender_confirmed_by": null,
                    "vendor_sends_reminders": false,
                    "live": true,
                    "blocked_reason": null,
                    "defaults": [],
                    "updated_at": "2026-10-11T12:00:00.000Z",
                    "confirm_status_id": null,
                    "confirm_status": null,
                    "create_contacts_from_patient_phone": true,
                    "messaging_account_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                    "accounts": [
                      {
                        "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                        "name": "Recepción",
                        "phone_number": "+56922223333",
                        "display": "Recepción · +56922223333",
                        "enabled": true
                      }
                    ],
                    "warnings": [
                      {
                        "rung_key": "welcome",
                        "code": "template_missing",
                        "message": "Plantilla (demo): no existe una plantilla «demo_confirmada_v1» en este número de WhatsApp; fuera de la ventana de 24 h no saldrá nada."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/appointments/reminders/plan/revoke-single-sender": {
      "post": {
        "summary": "Stop sending reminders",
        "description": "Clears the declaration and deactivates the ladder in the same write.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "responses": {
          "200": {
            "description": "Reminder plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "rungs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "single_sender_confirmed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "single_sender_confirmed_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vendor_sends_reminders": {
                          "type": "boolean"
                        },
                        "live": {
                          "type": "boolean"
                        },
                        "blocked_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "no_plan",
                            "inactive",
                            "single_sender_not_confirmed",
                            "vendor_sends_reminders",
                            "no_rungs"
                          ]
                        },
                        "defaults": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string"
                              },
                              "event": {
                                "type": "string",
                                "enum": [
                                  "before_start",
                                  "booked",
                                  "no_show",
                                  "host_changed"
                                ]
                              },
                              "email": {
                                "type": "string",
                                "enum": [
                                  "never",
                                  "fallback",
                                  "also"
                                ]
                              },
                              "templates": {
                                "type": "object",
                                "additionalProperties": {
                                  "type": "string"
                                }
                              },
                              "offset_hours": {
                                "type": "number"
                              },
                              "channel": {
                                "type": "string"
                              },
                              "template_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "asks_confirmation": {
                                "type": "boolean"
                              },
                              "template": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": "string"
                                  },
                                  "language": {
                                    "type": "string"
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "category": {
                                    "type": "string"
                                  },
                                  "body_preview": {
                                    "type": "string"
                                  },
                                  "variables": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "buttons": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "type": {
                                          "type": "string",
                                          "enum": [
                                            "QUICK_REPLY",
                                            "URL",
                                            "PHONE_NUMBER",
                                            "FLOW"
                                          ]
                                        },
                                        "text": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "type",
                                        "text"
                                      ]
                                    }
                                  },
                                  "usable": {
                                    "type": "boolean"
                                  },
                                  "reasons": {
                                    "type": "array",
                                    "items": {
                                      "type": "string",
                                      "enum": [
                                        "template_not_approved",
                                        "template_positional_variables",
                                        "template_unknown_variable",
                                        "template_unsupported"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "name",
                                  "language",
                                  "status",
                                  "category",
                                  "body_preview",
                                  "variables",
                                  "buttons",
                                  "usable",
                                  "reasons"
                                ]
                              },
                              "derived": {
                                "type": "object",
                                "properties": {
                                  "asks_confirmation": {
                                    "type": "boolean"
                                  },
                                  "includes_link": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "asks_confirmation",
                                  "includes_link"
                                ]
                              }
                            },
                            "required": [
                              "key",
                              "offset_hours",
                              "channel",
                              "template_name",
                              "asks_confirmation",
                              "template",
                              "derived"
                            ]
                          }
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "confirm_status_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "confirm_status": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "name": {
                              "type": "string"
                            },
                            "external_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "heuristic"
                              ]
                            }
                          },
                          "required": [
                            "id",
                            "name",
                            "external_id",
                            "source"
                          ]
                        },
                        "create_contacts_from_patient_phone": {
                          "type": "boolean"
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "accounts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "name": {
                                "type": "string"
                              },
                              "phone_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "display": {
                                "type": "string"
                              },
                              "enabled": {
                                "type": "boolean"
                              }
                            },
                            "required": [
                              "id",
                              "name",
                              "phone_number",
                              "display",
                              "enabled"
                            ]
                          }
                        },
                        "warnings": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rung_key": {
                                "type": "string"
                              },
                              "code": {
                                "type": "string",
                                "enum": [
                                  "no_template_outside_window",
                                  "template_missing",
                                  "template_not_approved",
                                  "template_positional_variables",
                                  "template_unknown_variable",
                                  "template_unsupported",
                                  "account_not_chosen"
                                ]
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "rung_key",
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "rungs",
                        "active",
                        "single_sender_confirmed_at",
                        "single_sender_confirmed_by",
                        "vendor_sends_reminders",
                        "live",
                        "blocked_reason",
                        "defaults",
                        "updated_at",
                        "confirm_status_id",
                        "confirm_status",
                        "create_contacts_from_patient_phone",
                        "messaging_account_id",
                        "accounts",
                        "warnings"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b10",
                    "name": "Recordatorios de cita",
                    "rungs": [
                      {
                        "key": "welcome",
                        "event": "booked",
                        "offset_hours": 0,
                        "channel": "whatsapp",
                        "email": "also",
                        "template_name": null,
                        "templates": {
                          "demo": "demo_confirmada_v1"
                        },
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "h24",
                        "offset_hours": 24,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "recordatorio_24h",
                        "asks_confirmation": true,
                        "template": {
                          "name": "recordatorio_24h",
                          "language": "es",
                          "status": "APPROVED",
                          "category": "UTILITY",
                          "body_preview": "Hola {{nombre}}, te recordamos tu cita del {{fecha}} a las {{hora}}. ¿Nos confirmas?",
                          "variables": [
                            "nombre",
                            "fecha",
                            "hora"
                          ],
                          "buttons": [
                            {
                              "type": "QUICK_REPLY",
                              "text": "Confirmo"
                            },
                            {
                              "type": "QUICK_REPLY",
                              "text": "No puedo asistir"
                            }
                          ],
                          "usable": true,
                          "reasons": []
                        },
                        "derived": {
                          "asks_confirmation": true,
                          "includes_link": false
                        }
                      },
                      {
                        "key": "h2",
                        "offset_hours": 2,
                        "channel": "whatsapp",
                        "template_name": "recordatorio_2h",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      },
                      {
                        "key": "no_show",
                        "event": "no_show",
                        "offset_hours": 0.17,
                        "channel": "whatsapp",
                        "email": "fallback",
                        "template_name": "reagendar",
                        "asks_confirmation": false,
                        "template": null,
                        "derived": {
                          "asks_confirmation": false,
                          "includes_link": true
                        }
                      }
                    ],
                    "active": true,
                    "single_sender_confirmed_at": "2026-10-11T12:00:00.000Z",
                    "single_sender_confirmed_by": null,
                    "vendor_sends_reminders": false,
                    "live": true,
                    "blocked_reason": null,
                    "defaults": [],
                    "updated_at": "2026-10-11T12:00:00.000Z",
                    "confirm_status_id": null,
                    "confirm_status": null,
                    "create_contacts_from_patient_phone": true,
                    "messaging_account_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                    "accounts": [
                      {
                        "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b20",
                        "name": "Recepción",
                        "phone_number": "+56922223333",
                        "display": "Recepción · +56922223333",
                        "enabled": true
                      }
                    ],
                    "warnings": [
                      {
                        "rung_key": "welcome",
                        "code": "template_missing",
                        "message": "Plantilla (demo): no existe una plantilla «demo_confirmada_v1» en este número de WhatsApp; fuera de la ventana de 24 h no saldrá nada."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/appointments/reminders": {
      "get": {
        "summary": "List sent appointment reminders",
        "description": "The send ledger: one row per appointment per rung (and per email twin, keyed `<rung>_email`) with the outcome, the reason when skipped, the manage link that went out and the person's confirm/cancel reply. Each row carries the cita (`patient_name`, `appointment_starts_at`). Filter by `from`/`to` (when the row was written, ISO 8601 with offset) and `status`; `meta.pagination.total` counts every matching row. A skip whose `data.suppressed` is `human_active` means a teammate wrote in that chat within the last 2 h, so the reminder stood down.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sent",
                "skipped",
                "failed"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Appointment reminders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b11",
                        "appointment_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b12",
                        "rung_key": "welcome",
                        "offset_minutes": 0,
                        "channel": "whatsapp",
                        "asks_confirmation": false,
                        "template_name": "demo_confirmada_v1",
                        "status": "sent",
                        "reason_code": null,
                        "detail": null,
                        "conversation_id": "0198f3c2-6d1e-7a52-9c5b-3f1d2e7a4b13",
                        "sent_at": "2026-10-11T12:01:00.000Z",
                        "manage_url": null,
                        "reply_kind": null,
                        "reply_text": null,
                        "replied_at": null,
                        "applied_status_id": null,
                        "apply_error": null,
                        "created_at": "2026-10-11T12:01:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/appointment-types": {
      "get": {
        "summary": "List appointment types",
        "description": "The per-tenant bookable catalogue, which is ALSO the «Servicios» catalogue: a servicio is an appointment type with a price (there is no `/services` resource). `kind` and `active_only` are the screen’s tabs and `q` is its search box — free text over name AND description, case-insensitive, with `%` and `_` escaped so \"50% off\" is a literal rather than a wildcard. All three COMPOSE: `?kind=service&active_only=true&q=limpieza` is \"the active services whose name or description mentions limpieza\". An empty or omitted `q` is simply no text filter.\n\nELIMINATED types (`deleted_at` non-null, see `DELETE /appointment-types/{id}`) are excluded from EVERY combination of these filters — including `active_only=false`, so they never come back as \"inactivos\". Every row returned here therefore carries `deleted_at: null`; the field is on the wire as an audit fact, not as a state to branch on.\n\n`price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "test_drive",
                  "external",
                  "service"
                ]
              }
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "active_only",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Appointment types",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "eeee0000-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Consulta de evaluación",
                      "description": "Primera visita: revisión y presupuesto.",
                      "kind": "service",
                      "engine": "native",
                      "external_ref": null,
                      "buffer_minutes": null,
                      "duration_minutes": 45,
                      "price_amount": 35000,
                      "price_clp": 35000,
                      "price_currency": "CLP",
                      "price_is_from": true,
                      "eligible_staff_ids": [],
                      "property_exclusive": false,
                      "is_active": true,
                      "is_default": false,
                      "deleted_at": null,
                      "created_at": "2026-09-22T12:50:27.458Z",
                      "updated_at": "2026-09-22T12:50:27.458Z"
                    },
                    {
                      "id": "eeee0000-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Test drive",
                      "description": "Prueba de manejo de 30 minutos, con ejecutivo.",
                      "kind": "test_drive",
                      "engine": "native",
                      "external_ref": null,
                      "buffer_minutes": null,
                      "duration_minutes": 30,
                      "price_amount": null,
                      "price_clp": null,
                      "price_currency": "CLP",
                      "price_is_from": false,
                      "eligible_staff_ids": [],
                      "property_exclusive": false,
                      "is_active": true,
                      "is_default": true,
                      "deleted_at": null,
                      "created_at": "2026-06-02T11:02:10.004Z",
                      "updated_at": "2026-06-02T11:02:10.004Z"
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an appointment type",
        "description": "Adds a bookable type to the catalogue — a servicio once it carries a price. `duration_minutes` is what sets the slot LENGTH when this type is booked, while the agenda grid stays `slot_minutes` (`GET /appointments/config`).\n\n`eligible_staff_ids` narrows who may take it: booking assigns round-robin from that pool, and an empty list means the workspace’s whole sales/professional team. `kind` defaults to `external`.\n\nThe NAME is unique among the live rows, so re-creating a type you eliminated is allowed and yields a NEW id.\n\n`price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.\n\n**Depósito / abono.** The deposit rule for this service is NOT on this resource: it is a `deposit_policy` row scoped to `appointment_type`, read and written through `GET|PUT /deposit-policies`. Each booking still takes a SNAPSHOT — the resolved amount and deadline land on the cita’s own `customer_obligation` — so editing the policy never changes what an already-booked customer was told to transfer.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "test_drive",
                      "external",
                      "service"
                    ]
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1440
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999
                  },
                  "price_currency": {
                    "type": "string",
                    "pattern": "^[A-Za-z]{3}$"
                  },
                  "price_is_from": {
                    "type": "boolean"
                  },
                  "eligible_staff_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "property_exclusive": {
                    "type": "boolean"
                  },
                  "is_active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "duration_minutes"
                ]
              },
              "example": {
                "name": "Consulta de evaluación",
                "description": "Primera visita: revisión y presupuesto.",
                "kind": "service",
                "duration_minutes": 45,
                "price_amount": 35000,
                "price_currency": "CLP",
                "price_is_from": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Appointment type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "id": "eeee0000-0000-4000-8000-000000000001",
                  "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                  "name": "Consulta de evaluación",
                  "description": "Primera visita: revisión y presupuesto.",
                  "kind": "service",
                  "engine": "native",
                  "external_ref": null,
                  "buffer_minutes": null,
                  "duration_minutes": 45,
                  "price_amount": 35000,
                  "price_clp": 35000,
                  "price_currency": "CLP",
                  "price_is_from": true,
                  "eligible_staff_ids": [],
                  "property_exclusive": false,
                  "is_active": true,
                  "is_default": false,
                  "deleted_at": null,
                  "created_at": "2026-09-22T12:50:27.458Z",
                  "updated_at": "2026-09-22T12:50:27.458Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/appointment-types/{id}": {
      "get": {
        "summary": "Get an appointment type",
        "description": "By uuid. An ELIMINATED type is a 404 here even though the appointments booked against it still resolve its name — the catalogue and the history answer different questions.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Appointment type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "eeee0000-0000-4000-8000-000000000001",
                  "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                  "name": "Consulta de evaluación",
                  "description": "Primera visita: revisión y presupuesto.",
                  "kind": "service",
                  "engine": "native",
                  "external_ref": null,
                  "buffer_minutes": null,
                  "duration_minutes": 45,
                  "price_amount": 35000,
                  "price_clp": 35000,
                  "price_currency": "CLP",
                  "price_is_from": true,
                  "eligible_staff_ids": [],
                  "property_exclusive": false,
                  "is_active": true,
                  "is_default": false,
                  "deleted_at": null,
                  "created_at": "2026-09-22T12:50:27.458Z",
                  "updated_at": "2026-09-22T12:50:27.458Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an appointment type",
        "description": "ELIMINAR — a TOMBSTONE, and NOT the same act as deactivating. It stamps `deleted_at` (and `is_active = false`), after which the type is gone from the catalogue for good: absent from `GET /appointment-types` on every tab, 404 on `GET` and `PATCH /appointment-types/{id}`, and unbookable — a new booking that names it is refused exactly as one naming an unknown id is.\n\nDEACTIVATING is the other verb and is unchanged: `PATCH { \"is_active\": false }`. That one is reversible and keeps the type in the catalogue under the \"Inactivos\" tab.\n\nNOTHING CASCADES. Appointments already booked against this type keep their `appointment_type_id` and stay fully readable — their calendar mirror and reminders still resolve the type by id so they can say what they were for. A second DELETE of the same id is a 404, so the recorded moment of removal is the first one.\n\nThe seeded default type (`is_default`) refuses deletion with a 400. An eliminated NAME is reusable — the uniqueness index is partial on the live rows — so re-creating a servicio you removed yields a NEW id, never the old row back.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit an appointment type",
        "description": "A true partial: only the keys present are written, so a body with one field never nulls the rest. `eligible_staff_ids` is REPLACED wholesale when present — send the full list, not a delta.\n\n`{ \"is_active\": false }` is DEACTIVATING: reversible, and the type stays in the catalogue under the \"Inactivos\" tab. Removing it for good is the other verb, `DELETE /appointment-types/{id}`.\n\nEditing a type never re-writes the appointments already booked against it: they keep the duration and the price they were booked with.\n\n`price_amount` is `null` for «a consultar» and `0` for free of charge — never interchange them, because a client renders one as a price and the other as an invitation to ask. `price_is_from` marks it as a floor («desde $35.000»). `price_currency` is a 3-letter ISO-4217 code, upper-cased on the way in, and `price_clp` is the same amount in pesos for a workspace that prices in another currency.\n\n**Depósito / abono.** The deposit rule for this service is NOT on this resource: it is a `deposit_policy` row scoped to `appointment_type`, read and written through `GET|PUT /deposit-policies`. Each booking still takes a SNAPSHOT — the resolved amount and deadline land on the cita’s own `customer_obligation` — so editing the policy never changes what an already-booked customer was told to transfer.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Appointments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "test_drive",
                      "external",
                      "service"
                    ]
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 1440
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999
                  },
                  "price_currency": {
                    "type": "string",
                    "pattern": "^[A-Za-z]{3}$"
                  },
                  "price_is_from": {
                    "type": "boolean"
                  },
                  "eligible_staff_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    }
                  },
                  "property_exclusive": {
                    "type": "boolean"
                  },
                  "is_active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "price_amount": 39000,
                "eligible_staff_ids": [
                  "11111111-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Appointment type",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "eeee0000-0000-4000-8000-000000000001",
                  "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                  "name": "Consulta de evaluación",
                  "description": "Primera visita: revisión y presupuesto.",
                  "kind": "service",
                  "engine": "native",
                  "external_ref": null,
                  "buffer_minutes": null,
                  "duration_minutes": 45,
                  "price_amount": 35000,
                  "price_clp": 35000,
                  "price_currency": "CLP",
                  "price_is_from": true,
                  "eligible_staff_ids": [],
                  "property_exclusive": false,
                  "is_active": true,
                  "is_default": false,
                  "deleted_at": null,
                  "created_at": "2026-09-22T12:50:27.458Z",
                  "updated_at": "2026-09-22T12:50:27.458Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences": {
      "get": {
        "summary": "List saved audiences",
        "description": "Every audience as a summary, newest first, with `meta.total`: the filter, its derived `kind` (`dynamic` rule · `static` hand-picked list · `mixed` · `all` = the whole directory), the override list SIZES (not the lists), `labels` resolving every tag / pipeline / stage / custom field the filter names (`labels.missing` lists references that no longer exist — they match nobody), and campaign `usage`. No membership is computed here; ask `GET /audiences/stats?ids=` for sizes. `q` searches the name; `kind` filters.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "dynamic",
                "static",
                "mixed",
                "all"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Audience summaries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AudienceSummary"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "total": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "total"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                      "name": "Pacientes con control anual",
                      "description": "Clientes con la etiqueta de control anual en Chile.",
                      "filter": {
                        "lifecycle_stages": [
                          "customer"
                        ],
                        "tag_ids_any": [
                          "7a7a7a7a-0000-4000-8000-0000000000e1"
                        ],
                        "countries": [
                          "Chile"
                        ],
                        "has_channel": [
                          "whatsapp"
                        ]
                      },
                      "kind": "dynamic",
                      "include_count": 0,
                      "exclude_count": 0,
                      "labels": {
                        "tags": {
                          "7a7a7a7a-0000-4000-8000-0000000000e1": "Control anual"
                        },
                        "pipelines": {},
                        "stages": {},
                        "custom_attributes": {},
                        "interest_topics": {},
                        "missing": {
                          "tags": [],
                          "pipeline_id": null,
                          "stage_id": null,
                          "custom_attributes": [],
                          "interest_topics": []
                        }
                      },
                      "usage": {
                        "campaigns_count": 2,
                        "last_used_at": "2026-09-20T12:00:00.000Z",
                        "active_campaigns_count": 1
                      },
                      "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                      "created_at": "2026-09-01T10:00:00.000Z",
                      "updated_at": "2026-09-15T10:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Save an audience",
        "description": "Only `name` is required. The filter is stored exactly as sent — no clause is added. An audience with no filter and no includes is valid and means **every contact in the workspace** — deliberate, but worth a confirmation step in a UI before it is pointed at a campaign.\n\nFilter clauses: `lifecycle_stages`, `tag_ids_any` / `tag_ids_all` / `tag_ids_none`, `countries`, `languages`, `has_channel`, `pipeline_id` (+ optional `stage_id`, matching contacts with an OPEN lead there), `created_after` / `created_before`, `birthday_months` / `birthday_within_days` (the workspace’s local today), `vehicle_brands` / `vehicle_models` / `vehicle_years` (the customer’s OWN current car, `contact.vehicle_*` — what they asked for is `interests`), and `custom_attributes` (`[{ key, values?: string[] }]` — any-of, trimmed, case- and accent-insensitive; `value` is accepted as `values: [value]`; neither means \"has any value\"), `interests` (`{ topic_ids, within_days?, min_strength?, include_past? }` — holds a projected interest in any of the topics from `GET /interest-topics`; a brand matches its models; `min_strength` 2 asked · 3 booked/quoted · 4 bought; by default only current, not superseded, interests) and `interests_none` (`{ topic_ids, min_strength?, include_past? }` — holds none of them; `include_past` defaults to true here, so «ya compró» survives supersession). A merged topic id is followed to its survivor. Clauses AND together; values inside one clause are any-of. The object is **strict**: an unrecognised key is a 400 rather than being ignored, so a typo cannot quietly widen an audience to everyone.\n\nThe two override lists are capped at 2.000 ids each and every id must be a contact of this workspace (422 `unknown_contact_ids` otherwise). Answers 201 with the audience detail.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "filter": {
                    "type": "object",
                    "properties": {
                      "lifecycle_stages": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 40
                        },
                        "maxItems": 10
                      },
                      "tag_ids_any": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 25
                      },
                      "tag_ids_all": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 10
                      },
                      "tag_ids_none": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 25
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60
                        },
                        "maxItems": 25
                      },
                      "languages": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "maxItems": 25
                      },
                      "has_channel": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "whatsapp",
                            "email"
                          ]
                        },
                        "maxItems": 2
                      },
                      "pipeline_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "stage_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "created_after": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "created_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "birthday_months": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 12
                        },
                        "maxItems": 12
                      },
                      "birthday_within_days": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 366
                      },
                      "vehicle_brands": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "maxItems": 25
                      },
                      "vehicle_models": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "maxItems": 25
                      },
                      "vehicle_years": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1900,
                          "maximum": 2100
                        },
                        "maxItems": 50
                      },
                      "custom_attributes": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "key": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 100
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 500
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 500
                              },
                              "maxItems": 25
                            }
                          },
                          "required": [
                            "key"
                          ]
                        },
                        "maxItems": 10
                      },
                      "interests": {
                        "type": "object",
                        "properties": {
                          "topic_ids": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "minItems": 1,
                            "maxItems": 25
                          },
                          "within_days": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 1095
                          },
                          "min_strength": {
                            "anyOf": [
                              {
                                "type": "number",
                                "enum": [
                                  1
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  2
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  3
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  4
                                ]
                              }
                            ]
                          },
                          "include_past": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "topic_ids"
                        ],
                        "additionalProperties": false
                      },
                      "interests_none": {
                        "type": "object",
                        "properties": {
                          "topic_ids": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "minItems": 1,
                            "maxItems": 25
                          },
                          "min_strength": {
                            "anyOf": [
                              {
                                "type": "number",
                                "enum": [
                                  1
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  2
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  3
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  4
                                ]
                              }
                            ]
                          },
                          "include_past": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "topic_ids"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "additionalProperties": false
                  },
                  "include_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 2000
                  },
                  "exclude_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 2000
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Pacientes con control anual",
                "description": "Clientes con la etiqueta de control anual en Chile.",
                "filter": {
                  "lifecycle_stages": [
                    "customer"
                  ],
                  "tag_ids_any": [
                    "7a7a7a7a-0000-4000-8000-0000000000e1"
                  ],
                  "countries": [
                    "Chile"
                  ],
                  "has_channel": [
                    "whatsapp"
                  ]
                },
                "exclude_contact_ids": [
                  "7a7a7a7a-0000-4000-8000-0000000000f2"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The saved audience",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceDetail"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "name": "Pacientes con control anual",
                    "description": "Clientes con la etiqueta de control anual en Chile.",
                    "filter": {
                      "lifecycle_stages": [
                        "customer"
                      ],
                      "tag_ids_any": [
                        "7a7a7a7a-0000-4000-8000-0000000000e1"
                      ],
                      "countries": [
                        "Chile"
                      ],
                      "has_channel": [
                        "whatsapp"
                      ]
                    },
                    "kind": "dynamic",
                    "include_count": 0,
                    "exclude_count": 1,
                    "labels": {
                      "tags": {
                        "7a7a7a7a-0000-4000-8000-0000000000e1": "Control anual"
                      },
                      "pipelines": {},
                      "stages": {},
                      "custom_attributes": {},
                      "interest_topics": {},
                      "missing": {
                        "tags": [],
                        "pipeline_id": null,
                        "stage_id": null,
                        "custom_attributes": [],
                        "interest_topics": []
                      }
                    },
                    "usage": {
                      "campaigns_count": 0,
                      "last_used_at": null,
                      "active_campaigns_count": 0,
                      "active_campaigns": []
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-01T10:00:00.000Z",
                    "updated_at": "2026-09-15T10:00:00.000Z",
                    "include_contact_ids": [],
                    "exclude_contact_ids": [
                      "7a7a7a7a-0000-4000-8000-0000000000f2"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/audiences/condition-data": {
      "get": {
        "summary": "Which data-dependent conditions can match anyone",
        "description": "What the audience editor needs to decide which conditions to offer: `vehicle` is true when some live contact of the workspace carries its own vehicle (brand, model or year) — without that data the `vehicle_*` clauses can only match nobody. One indexed EXISTS; cheap enough to call when the editor opens.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "responses": {
          "200": {
            "description": "Condition data",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceConditionData"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vehicle": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/stats": {
      "get": {
        "summary": "Sizes and reach for several audiences",
        "description": "For up to 50 comma-separated `ids`: `total` members and how many are reachable on WhatsApp and on email — the list page’s columns. Shares the 5-minute cache of `/{id}/counts` (`fresh=1` bypasses it). Unknown or foreign ids are omitted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "minItems": 1,
              "maxItems": 50
            },
            "required": true,
            "name": "ids",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "required": false,
            "name": "fresh",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-audience stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AudienceStats"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                      "total": 512,
                      "whatsapp_reachable": 470,
                      "email_reachable": 301,
                      "computed_at": "2026-10-01T15:00:00.000Z",
                      "partial": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/overlap": {
      "get": {
        "summary": "Overlap of two audiences",
        "description": "`ids=a,b` → members in both, only in A, only in B. Useful to check that two A/B test groups do not share people.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "minItems": 1,
              "maxItems": 2
            },
            "required": true,
            "name": "ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The overlap",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceOverlap"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "a": {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                      "total": 512
                    },
                    "b": {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000a2",
                      "total": 340
                    },
                    "both": 120,
                    "a_only": 392,
                    "b_only": 220
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/{id}": {
      "get": {
        "summary": "Fetch one audience",
        "description": "The summary plus both override lists (for API round-trips) and the campaigns that will still send to it (`usage.active_campaigns`: draft, scheduled, sending or paused). Membership is not included — see `/{id}/members` and `/{id}/counts`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The audience",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceDetail"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "name": "Pacientes con control anual",
                    "description": "Clientes con la etiqueta de control anual en Chile.",
                    "filter": {
                      "lifecycle_stages": [
                        "customer"
                      ],
                      "tag_ids_any": [
                        "7a7a7a7a-0000-4000-8000-0000000000e1"
                      ],
                      "countries": [
                        "Chile"
                      ],
                      "has_channel": [
                        "whatsapp"
                      ]
                    },
                    "kind": "dynamic",
                    "include_count": 0,
                    "exclude_count": 0,
                    "labels": {
                      "tags": {
                        "7a7a7a7a-0000-4000-8000-0000000000e1": "Control anual"
                      },
                      "pipelines": {},
                      "stages": {},
                      "custom_attributes": {},
                      "interest_topics": {},
                      "missing": {
                        "tags": [],
                        "pipeline_id": null,
                        "stage_id": null,
                        "custom_attributes": [],
                        "interest_topics": []
                      }
                    },
                    "usage": {
                      "campaigns_count": 2,
                      "last_used_at": "2026-09-20T12:00:00.000Z",
                      "active_campaigns_count": 1,
                      "active_campaigns": [
                        {
                          "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                          "name": "Recordatorio de control",
                          "status": "sending",
                          "scheduled_at": null
                        }
                      ]
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-01T10:00:00.000Z",
                    "updated_at": "2026-09-15T10:00:00.000Z",
                    "include_contact_ids": [],
                    "exclude_contact_ids": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an audience",
        "description": "Removes the definition. Contacts are untouched — an audience is a saved query, so deleting one never deletes anybody. **Refused with 409 `CONFLICT`, `details.reason = audience_in_use`** (and `details.campaigns`) while a draft, scheduled, sending or paused campaign uses it — move that campaign to another audience or cancel it first. Sent and cancelled campaigns keep their recipient list and do not block. 404 for an id that is not there.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update an audience",
        "description": "Only the fields present are applied; at least one is required. `filter`, `include_contact_ids` and `exclude_contact_ids` are each REPLACED wholesale when sent, never merged — send `filter: {}` and the audience widens to every contact. To add or remove single people use `POST /audiences/{id}/overrides` instead.\n\n**Concurrency:** send `if_updated_at` (the `updated_at` you loaded) and a newer edit answers 409 `VERSION_CONFLICT` with `details.current_updated_at` instead of being overwritten.\n\nEditing takes effect on the next read. Campaigns that already sent are unaffected (they snapshot their recipients), but a scheduled campaign pointed at this audience will use the NEW definition when it runs.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "filter": {
                    "type": "object",
                    "properties": {
                      "lifecycle_stages": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 40
                        },
                        "maxItems": 10
                      },
                      "tag_ids_any": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 25
                      },
                      "tag_ids_all": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 10
                      },
                      "tag_ids_none": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "maxItems": 25
                      },
                      "countries": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60
                        },
                        "maxItems": 25
                      },
                      "languages": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 20
                        },
                        "maxItems": 25
                      },
                      "has_channel": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "enum": [
                            "whatsapp",
                            "email"
                          ]
                        },
                        "maxItems": 2
                      },
                      "pipeline_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "stage_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "created_after": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "created_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "birthday_months": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 12
                        },
                        "maxItems": 12
                      },
                      "birthday_within_days": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 366
                      },
                      "vehicle_brands": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "maxItems": 25
                      },
                      "vehicle_models": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "maxItems": 25
                      },
                      "vehicle_years": {
                        "type": "array",
                        "items": {
                          "type": "integer",
                          "minimum": 1900,
                          "maximum": 2100
                        },
                        "maxItems": 50
                      },
                      "custom_attributes": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "key": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 100
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 500
                            },
                            "values": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 500
                              },
                              "maxItems": 25
                            }
                          },
                          "required": [
                            "key"
                          ]
                        },
                        "maxItems": 10
                      },
                      "interests": {
                        "type": "object",
                        "properties": {
                          "topic_ids": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "minItems": 1,
                            "maxItems": 25
                          },
                          "within_days": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 1095
                          },
                          "min_strength": {
                            "anyOf": [
                              {
                                "type": "number",
                                "enum": [
                                  1
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  2
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  3
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  4
                                ]
                              }
                            ]
                          },
                          "include_past": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "topic_ids"
                        ],
                        "additionalProperties": false
                      },
                      "interests_none": {
                        "type": "object",
                        "properties": {
                          "topic_ids": {
                            "type": "array",
                            "items": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "minItems": 1,
                            "maxItems": 25
                          },
                          "min_strength": {
                            "anyOf": [
                              {
                                "type": "number",
                                "enum": [
                                  1
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  2
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  3
                                ]
                              },
                              {
                                "type": "number",
                                "enum": [
                                  4
                                ]
                              }
                            ]
                          },
                          "include_past": {
                            "type": "boolean"
                          }
                        },
                        "required": [
                          "topic_ids"
                        ],
                        "additionalProperties": false
                      }
                    },
                    "additionalProperties": false
                  },
                  "include_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 2000
                  },
                  "exclude_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 2000
                  },
                  "if_updated_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              },
              "example": {
                "name": "Pacientes con control anual (Chile)",
                "filter": {
                  "lifecycle_stages": [
                    "customer"
                  ],
                  "tag_ids_any": [
                    "7a7a7a7a-0000-4000-8000-0000000000e1"
                  ],
                  "countries": [
                    "Chile"
                  ],
                  "has_channel": [
                    "whatsapp"
                  ],
                  "languages": [
                    "es"
                  ]
                },
                "if_updated_at": "2026-09-15T10:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated audience",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceDetail"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "name": "Pacientes con control anual (Chile)",
                    "description": "Clientes con la etiqueta de control anual en Chile.",
                    "filter": {
                      "lifecycle_stages": [
                        "customer"
                      ],
                      "tag_ids_any": [
                        "7a7a7a7a-0000-4000-8000-0000000000e1"
                      ],
                      "countries": [
                        "Chile"
                      ],
                      "has_channel": [
                        "whatsapp"
                      ],
                      "languages": [
                        "es"
                      ]
                    },
                    "kind": "dynamic",
                    "include_count": 0,
                    "exclude_count": 0,
                    "labels": {
                      "tags": {
                        "7a7a7a7a-0000-4000-8000-0000000000e1": "Control anual"
                      },
                      "pipelines": {},
                      "stages": {},
                      "custom_attributes": {},
                      "interest_topics": {},
                      "missing": {
                        "tags": [],
                        "pipeline_id": null,
                        "stage_id": null,
                        "custom_attributes": [],
                        "interest_topics": []
                      }
                    },
                    "usage": {
                      "campaigns_count": 2,
                      "last_used_at": "2026-09-20T12:00:00.000Z",
                      "active_campaigns_count": 1,
                      "active_campaigns": [
                        {
                          "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                          "name": "Recordatorio de control",
                          "status": "sending",
                          "scheduled_at": null
                        }
                      ]
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-01T10:00:00.000Z",
                    "updated_at": "2026-10-01T14:00:00.000Z",
                    "include_contact_ids": [],
                    "exclude_contact_ids": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/{id}/counts": {
      "get": {
        "summary": "Size and reach per channel",
        "description": "`total` members; `by_source` (rule / manual / both); `overrides` (list sizes, includes that are not members, exclusions that would otherwise match); `omitted` (match the rule but are not members: merged, blocked, spam, archived, excluded by hand, import-only); and `channels.whatsapp` / `channels.email`: `reachable`, `no_identifier`, `invalid_identifier` (+ `invalid_by`), `duplicate`, `opted_out` (+ `opted_out_by` reason). Per channel the five add up to `total`, and **`reachable` is the number to show** — a campaign writes `reachable + opted_out` recipient rows and sends to `reachable`.\n\n`partial: true` means the audience is larger than `cap` (the most a campaign sends to); the counts themselves are exact. Cached 5 minutes (`computed_at`); `fresh=1` recomputes. `whatsapp_eligible` / `whatsapp_suppressed` / `suppressed_is_partial` are transitional and will be removed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            },
            "required": false,
            "name": "fresh",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Audience size and reach",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/AudienceCounts"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "total": 512,
                    "by_source": {
                      "rule": 500,
                      "manual": 8,
                      "rule_and_manual": 4
                    },
                    "overrides": {
                      "included": 12,
                      "included_not_member": 1,
                      "excluded": 3,
                      "excluded_would_match": 3
                    },
                    "omitted": {
                      "archived": 4,
                      "blocked": 1,
                      "spam": 0,
                      "merged": 2,
                      "import_only": 0,
                      "excluded": 3
                    },
                    "channels": {
                      "whatsapp": {
                        "reachable": 470,
                        "no_identifier": 0,
                        "invalid_identifier": 0,
                        "duplicate": 0,
                        "opted_out": 0,
                        "opted_out_by": {},
                        "invalid_by": {}
                      },
                      "email": {
                        "reachable": 301,
                        "no_identifier": 0,
                        "invalid_identifier": 0,
                        "duplicate": 0,
                        "opted_out": 0,
                        "opted_out_by": {},
                        "invalid_by": {}
                      }
                    },
                    "computed_at": "2026-10-01T15:00:00.000Z",
                    "partial": false,
                    "cap": 100000,
                    "whatsapp_eligible": 470,
                    "whatsapp_suppressed": 0,
                    "suppressed_is_partial": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/{id}/overrides": {
      "post": {
        "summary": "Add or remove people by hand",
        "description": "`action`: `include` (always in, even when the rule does not match; drops them from the exclusions), `exclude` (never in; drops them from the includes) or `reset` (back to \"the rule decides\"). Send `contact_ids` or `identifiers` (phones / emails of this workspace’s contacts; include/exclude only), up to 500 per call. Atomic against the stored lists. Each input gets an outcome: `added`, `removed`, `already`, `not_found`, `moderated` (merged/archived/blocked/spam — never a member, so not included) or `already_member_by_rule` (stored anyway, so they stay if the rule changes). A list past 2.000 ids is a 422 with `details.reason = override_cap`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "include",
                      "exclude",
                      "reset"
                    ]
                  },
                  "contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  },
                  "identifiers": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 254
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "action"
                ]
              },
              "example": {
                "action": "include",
                "contact_ids": [
                  "7a7a7a7a-0000-4000-8000-0000000000f1",
                  "7a7a7a7a-0000-4000-8000-0000000000f2"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What happened to each input",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceOverridesResult"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "applied": 1,
                    "outcomes": [
                      {
                        "contact_id": "7a7a7a7a-0000-4000-8000-0000000000f1",
                        "result": "added"
                      },
                      {
                        "contact_id": "7a7a7a7a-0000-4000-8000-0000000000f2",
                        "result": "already_member_by_rule"
                      }
                    ],
                    "include_count": 1,
                    "exclude_count": 0,
                    "updated_at": "2026-10-01T14:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/audiences/{id}/duplicate": {
      "post": {
        "summary": "Duplicate an audience",
        "description": "A copy named «Copia de {name}» (or `name`). `freeze: true` instead saves TODAY’s members as a fixed list (no filter, includes = the members) — up to 2.000 people; larger is a 422 with `details.reason = freeze_too_large`. Answers 201 with the new audience.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Audiences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "freeze": {
                    "type": "boolean",
                    "default": false
                  }
                }
              },
              "example": {
                "name": "Pacientes con control anual (congelada)",
                "freeze": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new audience",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AudienceDetail"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a2",
                    "name": "Pacientes con control anual (congelada)",
                    "description": "Clientes con la etiqueta de control anual en Chile.",
                    "filter": {},
                    "kind": "static",
                    "include_count": 512,
                    "exclude_count": 0,
                    "labels": {
                      "tags": {
                        "7a7a7a7a-0000-4000-8000-0000000000e1": "Control anual"
                      },
                      "pipelines": {},
                      "stages": {},
                      "custom_attributes": {},
                      "interest_topics": {},
                      "missing": {
                        "tags": [],
                        "pipeline_id": null,
                        "stage_id": null,
                        "custom_attributes": [],
                        "interest_topics": []
                      }
                    },
                    "usage": {
                      "campaigns_count": 0,
                      "last_used_at": null,
                      "active_campaigns_count": 0,
                      "active_campaigns": []
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-01T14:20:00.000Z",
                    "include_contact_ids": [
                      "7a7a7a7a-0000-4000-8000-0000000000f1",
                      "7a7a7a7a-0000-4000-8000-0000000000f2"
                    ],
                    "exclude_contact_ids": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns": {
      "get": {
        "summary": "List campaigns",
        "description": "Filter by `status`, `channel` and/or `audience_id` (the campaigns that used or will use one audience). Paginated with `limit` (default 50, max 100) and `offset`; the count comes back as `meta.pagination.total`, NOT `meta.total`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "scheduled",
                "sending",
                "paused",
                "sent",
                "cancelled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "whatsapp",
                "email",
                "voice"
              ]
            },
            "required": false,
            "name": "channel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "audience_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Campaigns for the workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                      "name": "Recordatorio de control",
                      "channel": "whatsapp",
                      "status": "sending",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                      "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                      "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                      "template_params": {
                        "1": {
                          "source": "contact_field",
                          "field": "first_name",
                          "fallback": "cliente"
                        },
                        "2": {
                          "source": "static",
                          "value": "15 de noviembre"
                        }
                      },
                      "content": {},
                      "reply_mode": "account_default",
                      "scheduled_at": null,
                      "started_at": "2026-10-01T15:00:00.000Z",
                      "finished_at": null,
                      "paused_reason": null,
                      "send_mode": "paced",
                      "pilot_size": null,
                      "pilot_completed_at": null,
                      "pacing_next_batch_at": null,
                      "total_recipients": 500,
                      "suppressed_count": 12,
                      "sent_count": 120,
                      "delivered_count": 110,
                      "read_count": 64,
                      "failed_count": 0,
                      "replied_count": 0,
                      "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-01T14:00:00.000Z"
                    },
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000c2",
                      "name": "Promoción de octubre",
                      "channel": "whatsapp",
                      "status": "draft",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                      "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                      "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                      "template_params": {
                        "1": {
                          "source": "contact_field",
                          "field": "first_name",
                          "fallback": "cliente"
                        },
                        "2": {
                          "source": "static",
                          "value": "15 de noviembre"
                        }
                      },
                      "content": {},
                      "reply_mode": "account_default",
                      "scheduled_at": null,
                      "started_at": null,
                      "finished_at": null,
                      "paused_reason": null,
                      "send_mode": "paced",
                      "pilot_size": null,
                      "pilot_completed_at": null,
                      "pacing_next_batch_at": null,
                      "total_recipients": 0,
                      "suppressed_count": 0,
                      "sent_count": 0,
                      "delivered_count": 0,
                      "read_count": 0,
                      "failed_count": 0,
                      "replied_count": 0,
                      "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-01T14:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 2,
                      "limit": 50
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a campaign",
        "description": "Creates it in `draft`. Nothing is validated for sendability here — a campaign with no audience and no content is a perfectly legal draft, and the gates only run at schedule/send time.\n\nWhich fields apply depends on `channel`: `email` uses `content` (subject, html, from_local, and an opaque `design` blob the builder reloads its canvas from); `whatsapp` uses `template_id` plus `template_params`, which maps each template variable to a contact field or a static value with an optional fallback; `voice` uses neither. Passing `scheduled_at` here records the intent but does NOT schedule the campaign — call `/schedule` for that. Answers 201.\n\n`send_mode` (WhatsApp, ADR 0129): `paced` (default) continues across the rolling 24 h windows of the number’s messaging limit by itself; `asap` sends what fits now and pauses (`budget_exhausted`). `pilot_size` (1–1000) sends that many random recipients first and pauses (`pilot`) until resumed. `send_window` (WhatsApp): when paced batches may leave — `{ timezone (IANA), days [1–7], start \"HH:MM\", end \"HH:MM\" }`, start < end, same day; null = the workspace default. An invalid one is a 400 with `field_errors`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "channel": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "email",
                      "voice"
                    ]
                  },
                  "audience_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "messaging_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "template_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "template_params": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "contact_field",
                            "static"
                          ]
                        },
                        "field": {
                          "type": "string",
                          "enum": [
                            "name",
                            "first_name",
                            "email",
                            "phone",
                            "country",
                            "language"
                          ]
                        },
                        "value": {
                          "type": "string",
                          "maxLength": 500
                        },
                        "fallback": {
                          "type": "string",
                          "maxLength": 500
                        }
                      },
                      "required": [
                        "source"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "content": {
                    "type": "object",
                    "properties": {
                      "subject": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 300
                      },
                      "html": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500000
                      },
                      "from_local": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z0-9._-]+$"
                      },
                      "from_name": {
                        "type": "string",
                        "maxLength": 120
                      },
                      "design": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "reply_mode": {
                    "type": "string",
                    "enum": [
                      "account_default",
                      "human"
                    ]
                  },
                  "scheduled_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "send_mode": {
                    "type": "string",
                    "enum": [
                      "paced",
                      "asap"
                    ],
                    "description": "paced: continue automatically across the rolling 24 h windows; asap: send what fits now and pause (ADR 0129)."
                  },
                  "pilot_size": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 1000,
                    "description": "Pilot: send this many random recipients first, then pause (paused_reason \"pilot\") until resumed. Null = no pilot."
                  },
                  "send_window": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CampaignSendWindow"
                      },
                      {
                        "description": "When paced batches may leave (WhatsApp, ADR 0129). Null = the workspace default (`settings.campaigns.default_send_window`), else the marketing contact hours only. `asap` campaigns ignore it; pilots respect it."
                      }
                    ]
                  },
                  "send_schedule": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CampaignSendSchedule"
                      },
                      {
                        "description": "When paced batches leave, as explicit instants (WhatsApp, ADR 0139). Replaces the send window when set; null = the send window / contact hours. Refused with an `asap` send_mode (400 schedule_requires_paced)."
                      }
                    ]
                  }
                },
                "required": [
                  "name",
                  "channel"
                ]
              },
              "example": {
                "name": "Recordatorio de control",
                "channel": "whatsapp",
                "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                "template_params": {
                  "1": {
                    "source": "contact_field",
                    "field": "first_name",
                    "fallback": "cliente"
                  },
                  "2": {
                    "source": "static",
                    "value": "15 de noviembre"
                  }
                },
                "send_mode": "paced"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "draft",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": null,
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 0,
                    "suppressed_count": 0,
                    "sent_count": 0,
                    "delivered_count": 0,
                    "read_count": 0,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/campaigns/messaging-budget": {
      "get": {
        "summary": "A WhatsApp number's messaging-limit budget, and a send plan",
        "description": "Meta caps how many unique people a WhatsApp business may START a conversation with (a template outside the customer’s 24 h window) in a rolling 24 h — the messaging-limit tier (TIER_250, TIER_2K, …), shared by the numbers of one portfolio. `budget` is where the number stands: `limit_per_24h` (null = no cap: unlimited, or `tier_unknown`), the unique people messaged in the last 24 h by every template sender (`used_last_24h`, split campaigns / others), the `reserve` campaigns leave for everyone else — max(20 % of the limit, the p95 of the number’s own daily non-campaign usage over 7 days), capped at 80 % — and the `campaign_headroom` left right now.\n\nWith `recipients`, `plan` says how a campaign to that many people would go: `sendable_now` (its fair share of the headroom, shared with the other campaigns sending on the number), `windows` (sending days), `estimated_completion_at` (continuations move into the workspace’s marketing hours) and the `recommended_send_mode`. `campaign_id` makes a saved campaign’s own recent sends count as its. 404 for a number that is not this workspace’s, 400 for one that is not WhatsApp.\n\n`send_window` (JSON-encoded, optional) plans against that window — `null` = the workspace default; absent = the saved campaign’s effective window (with `campaign_id`), else the workspace default. The plan then answers `send_window` (resolved), `window_label` («lun–vie 10:00–18:00 (America/Santiago)») and, when it is outside the window now, `next_window_opens_at` — the first batch, `next_batch_at` and `estimated_completion_at` start there, and every continuation lands inside the window. An invalid window is a 400 with `field_errors` (`send_window.start`, …).\n\n`send_schedule` (JSON-encoded `CampaignSendSchedule`, optional; `null` = none; absent = the saved campaign’s) plans against explicit instants instead (ADR 0139): `plan.schedule` simulates it batch by batch against the rolling 24 h — `planned` per slot, per-slot `warnings` (`budget_not_freed`, `partial_budget`, `outside_contact_hours` — only when the team answers the replies, see `reply_mode` —, `in_the_past`), `covers_all`, `completes_at` and `issues` (`slot_in_past`, `duplicate_slot`, `too_close` with a `suggested_at`, `uncovered`, `asap_not_allowed`). The window fields are then null: a schedule replaces the window.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "messaging_account_id",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 1000000
            },
            "required": false,
            "name": "recipients",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "campaign_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 1000,
              "description": "JSON-encoded CampaignSendWindow to plan with (e.g. {\"timezone\":\"America/Santiago\",\"days\":[1,2,3,4,5],\"start\":\"10:00\",\"end\":\"18:00\"}), or `null` for the workspace default. Absent = the campaign's effective window (with campaign_id), else the workspace default."
            },
            "required": false,
            "description": "JSON-encoded CampaignSendWindow to plan with (e.g. {\"timezone\":\"America/Santiago\",\"days\":[1,2,3,4,5],\"start\":\"10:00\",\"end\":\"18:00\"}), or `null` for the workspace default. Absent = the campaign's effective window (with campaign_id), else the workspace default.",
            "name": "send_window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 10000,
              "description": "JSON-encoded CampaignSendSchedule to plan with (e.g. {\"kind\":\"daily\",\"timezone\":\"America/Santiago\",\"start_date\":\"2026-10-14\",\"time\":\"20:00\"}), or `null` for none. Absent = the saved campaign's schedule (with campaign_id), else none. With a schedule, `plan.schedule` carries the simulation."
            },
            "required": false,
            "description": "JSON-encoded CampaignSendSchedule to plan with (e.g. {\"kind\":\"daily\",\"timezone\":\"America/Santiago\",\"start_date\":\"2026-10-14\",\"time\":\"20:00\"}), or `null` for none. Absent = the saved campaign's schedule (with campaign_id), else none. With a schedule, `plan.schedule` carries the simulation.",
            "name": "send_schedule",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "account_default",
                "human"
              ],
              "description": "Who answers the replies, to plan with. `account_default` resolves the number's routing (an AI agent answers → slots outside the contact hours are not warned about). Absent = the saved campaign's (with campaign_id), else unknown (the warning stays)."
            },
            "required": false,
            "description": "Who answers the replies, to plan with. `account_default` resolves the number's routing (an AI agent answers → slots outside the contact hours are not warned about). Absent = the saved campaign's (with campaign_id), else unknown (the warning stays).",
            "name": "reply_mode",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The budget and, with `recipients`, the plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignMessagingBudget"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "budget": {
                      "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                      "account_ids": [
                        "7a7a7a7a-0000-4000-8000-0000000000b1"
                      ],
                      "waba_id": null,
                      "tier": "TIER_1K",
                      "tier_unknown": false,
                      "unlimited": false,
                      "quality_rating": "GREEN",
                      "tier_observed_at": "2026-10-01T08:00:00.000Z",
                      "limit_per_24h": 1000,
                      "used_last_24h": 180,
                      "used_by_campaigns_last_24h": 120,
                      "used_by_other_senders_last_24h": 60,
                      "remaining": 820,
                      "reserve": 100,
                      "reserve_basis": {
                        "pct_floor": 100,
                        "observed_p95": 60
                      },
                      "campaign_cap": 900,
                      "campaign_headroom": 780,
                      "window_resets_at": "2026-10-02T08:00:00.000Z",
                      "next_batch_at": null,
                      "computed_at": "2026-10-01T15:30:00.000Z"
                    },
                    "plan": {
                      "recipients": 1500,
                      "sendable_now": 780,
                      "fits_now": false,
                      "per_window": 900,
                      "windows": 2,
                      "estimated_completion_at": "2026-10-02T16:00:00.000Z",
                      "recommended_send_mode": "paced",
                      "next_batch_at": "2026-10-02T08:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}": {
      "get": {
        "summary": "Fetch one campaign with its stats",
        "description": "The campaign plus its delivery counters — this is the read to poll while a send is in progress. A WhatsApp campaign that is not finished also carries `pacing` (ADR 0129): its number’s tier and `limit_per_24h`, `sent_last_24h`, `pending`, `waiting` (a paced campaign waiting for the window) with `next_batch_at`, `estimated_completion_at` and `windows_remaining`, plus the effective `send_window` (`send_window_source`: campaign | workspace), its `window_label` and `next_window_opens_at` (outside the window now); null when the budget cannot be read. With a `send_schedule` (ADR 0139) it also carries `schedule_label`, `next_slot_at`, `batches` (what each slot did: planned / sent / failed / skipped_reason, oldest first) and `schedule` — the pending part simulated slot by slot. A campaign paused with `paused_reason: delivery_guard` carries `paused_detail` (`sends`, `undeliverable` 131026, `withheld` 131049, `rate` 0-1), computed from the recipients; null otherwise.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign and its stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "sending",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": "2026-10-01T15:00:00.000Z",
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 120,
                    "delivered_count": 110,
                    "read_count": 64,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T14:00:00.000Z",
                    "recipient_counts": {
                      "pending": 380,
                      "sent": 10,
                      "delivered": 46,
                      "read": 64,
                      "failed": 0,
                      "suppressed": 12
                    },
                    "pacing": {
                      "send_mode": "paced",
                      "tier": "TIER_1K",
                      "tier_unknown": false,
                      "limit_per_24h": 1000,
                      "sent_last_24h": 120,
                      "pending": 380,
                      "waiting": false,
                      "next_batch_at": null,
                      "estimated_completion_at": "2026-10-01T16:00:00.000Z",
                      "windows_remaining": 1,
                      "sendable_now": 380,
                      "campaign_headroom": 760
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a campaign",
        "description": "Only `draft` and `cancelled` campaigns can be deleted; anything else is a **409**. A campaign that has sent is a record of messages that reached real people and stays. Cancel first if you need it gone.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a campaign",
        "description": "Legal only while the campaign is `draft` or `scheduled`; from any other status this is a **409** — except a body carrying ONLY `send_window` and/or `send_schedule`, which is also legal while `paused` or `sending` and takes effect at the next batch (a campaign waiting for the old window or slot re-plans at once). At least one field is required. A `send_schedule` with `send_mode: asap` (set or stored) is a 400 `schedule_requires_paced`; a slot in the past or two slots at once a 400 `schedule_invalid`. `channel` is absent from the body on purpose — a campaign’s channel is fixed at creation, since the content shape depends on it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "audience_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "messaging_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "template_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "template_params": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "contact_field",
                            "static"
                          ]
                        },
                        "field": {
                          "type": "string",
                          "enum": [
                            "name",
                            "first_name",
                            "email",
                            "phone",
                            "country",
                            "language"
                          ]
                        },
                        "value": {
                          "type": "string",
                          "maxLength": 500
                        },
                        "fallback": {
                          "type": "string",
                          "maxLength": 500
                        }
                      },
                      "required": [
                        "source"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "content": {
                    "type": "object",
                    "properties": {
                      "subject": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 300
                      },
                      "html": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 500000
                      },
                      "from_local": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[a-z0-9._-]+$"
                      },
                      "from_name": {
                        "type": "string",
                        "maxLength": 120
                      },
                      "design": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    }
                  },
                  "reply_mode": {
                    "type": "string",
                    "enum": [
                      "account_default",
                      "human"
                    ]
                  },
                  "send_mode": {
                    "type": "string",
                    "enum": [
                      "paced",
                      "asap"
                    ],
                    "description": "paced: continue automatically across the rolling 24 h windows; asap: send what fits now and pause (ADR 0129)."
                  },
                  "pilot_size": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 1000,
                    "description": "Pilot: send this many random recipients first, then pause (paused_reason \"pilot\") until resumed. Null = no pilot."
                  },
                  "send_window": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CampaignSendWindow"
                      },
                      {
                        "description": "When paced batches may leave (WhatsApp, ADR 0129). Null = the workspace default (`settings.campaigns.default_send_window`), else the marketing contact hours only. `asap` campaigns ignore it; pilots respect it."
                      }
                    ]
                  },
                  "send_schedule": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CampaignSendSchedule"
                      },
                      {
                        "description": "When paced batches leave, as explicit instants (WhatsApp, ADR 0139). Replaces the send window when set; null = the send window / contact hours. Refused with an `asap` send_mode (400 schedule_requires_paced)."
                      }
                    ]
                  }
                }
              },
              "example": {
                "name": "Recordatorio de control (octubre)",
                "send_mode": "asap",
                "pilot_size": 50
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control (octubre)",
                    "channel": "whatsapp",
                    "status": "draft",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": null,
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "asap",
                    "pilot_size": 50,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 0,
                    "suppressed_count": 0,
                    "sent_count": 0,
                    "delivered_count": 0,
                    "read_count": 0,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T14:30:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/header-media": {
      "put": {
        "summary": "Change a WhatsApp campaign's media-header picture",
        "description": "Replaces the media header (image / video / PDF) of a WhatsApp campaign — the one change still legal while it is `sending` or `paused` besides the send window/schedule; also legal in `draft` and `scheduled`. Body `{ url }`: a public https link (e.g. from `POST /whatsapp-templates/header-media`).\n\nIn one transaction it sets `template_params.header_media = { source: 'static', value: url }` and rewrites the frozen `params.header_media` of every recipient still `pending`, so each one not yet sent gets the new picture. Recipients already sent, delivered, read or failed are history and keep the one they received. A batch the worker had already picked up when this commits may still leave with the old picture; the next batch carries the new one.\n\nThe **200** is the campaign detail (the same `data` as `GET /campaigns/{id}`) with `meta.updated_recipients` — how many pending recipients changed.\n\nRefusals (`error.details.reason`): **409 `campaign_closed`** — the campaign is `sent` or `cancelled`; **400 `header_media_not_supported`** — not a WhatsApp campaign, or its template has no media header; **400 `header_media_invalid`** — the url is not a public https link (the same rule as `template_params.header_media` on create/PATCH). Fires the `campaign.updated` realtime event.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignHeaderMediaBody"
              },
              "example": {
                "url": "https://media.example.com/promo-noviembre.jpg"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The campaign detail and how many pending recipients got the new link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "$ref": "#/components/schemas/CampaignHeaderMediaMeta"
                    }
                  },
                  "required": [
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "sending",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      },
                      "header_media": {
                        "source": "static",
                        "value": "https://media.example.com/promo-noviembre.jpg"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": "2026-10-01T15:00:00.000Z",
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 120,
                    "delivered_count": 110,
                    "read_count": 64,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T15:10:00.000Z",
                    "recipient_counts": {
                      "pending": 380,
                      "sent": 10,
                      "delivered": 46,
                      "read": 64,
                      "failed": 0,
                      "suppressed": 12
                    },
                    "pacing": {
                      "send_mode": "paced",
                      "tier": "TIER_1K",
                      "tier_unknown": false,
                      "limit_per_24h": 1000,
                      "sent_last_24h": 120,
                      "pending": 380,
                      "waiting": false,
                      "next_batch_at": null,
                      "estimated_completion_at": "2026-10-01T16:00:00.000Z",
                      "windows_remaining": 1,
                      "sendable_now": 380,
                      "campaign_headroom": 760
                    },
                    "paused_detail": null
                  },
                  "meta": {
                    "updated_recipients": 380
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/schedule": {
      "post": {
        "summary": "Schedule the campaign",
        "description": "Before a campaign may leave draft it must pass the readiness gates, and each unmet one is its own 400: an audience must be set and still exist; an EMAIL campaign additionally needs subject/body/from and a VERIFIED sending domain; a WHATSAPP campaign needs a template that is APPROVED on the same messaging account it will send from, with every template variable mapped. Voice campaigns have their own gates and no template — the agent speaks the call.\n\nAn EMAIL campaign never leaves from a person's connected mailbox (ADR 0108 §Decision 10, ADR 0032): it goes out through the campaign sending provider under the verified sending domain, and the connected mailbox may only be its Reply-To. A campaign that would resolve to a connected mailbox — the workspace has no verified sending domain but has a connected mailbox, or the From address IS a connected mailbox's address — is refused whole with a **422 `CAMPAIGN_FROM_CONNECTED_MAILBOX`** (`error.details`: `reason` `no_campaign_domain` | `from_is_mailbox`, `address`, `messaging_account_id`), before any recipient is written or queued. This check runs FIRST, before the first-campaign platform approval gate: where mail leaves from is decided before whether the workspace may campaign.\n\n`scheduled_at` must be in the future — a timestamp more than a minute in the past is a 400, though a slightly-past one is tolerated for clock skew. Legal from `draft` and from `scheduled` (rescheduling), and a 409 from anywhere else.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduled_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                },
                "required": [
                  "scheduled_at"
                ]
              },
              "example": {
                "scheduled_at": "2026-10-05T13:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scheduled campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "scheduled",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": "2026-10-05T13:00:00.000Z",
                    "started_at": null,
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 0,
                    "delivered_count": 0,
                    "read_count": 0,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T14:40:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`CAMPAIGN_FROM_CONNECTED_MAILBOX` — the email campaign would leave from a person's connected mailbox (ADR 0108 §Decision 10).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/send": {
      "post": {
        "summary": "Send the campaign now",
        "description": "\"Now\" means scheduled-for-this-instant. **The campaign this returns is in status `scheduled`, not `sending`** — the worker moves it to `sending` when it picks the run up. A client that waits for `sending` in this response waits forever; poll `GET /campaigns/{id}` instead.\n\nBefore a campaign may leave draft it must pass the readiness gates, and each unmet one is its own 400: an audience must be set and still exist; an EMAIL campaign additionally needs subject/body/from and a VERIFIED sending domain; a WHATSAPP campaign needs a template that is APPROVED on the same messaging account it will send from, with every template variable mapped. Voice campaigns have their own gates and no template — the agent speaks the call.\n\nAn EMAIL campaign never leaves from a person's connected mailbox (ADR 0108 §Decision 10, ADR 0032): it goes out through the campaign sending provider under the verified sending domain, and the connected mailbox may only be its Reply-To. A campaign that would resolve to a connected mailbox — the workspace has no verified sending domain but has a connected mailbox, or the From address IS a connected mailbox's address — is refused whole with a **422 `CAMPAIGN_FROM_CONNECTED_MAILBOX`** (`error.details`: `reason` `no_campaign_domain` | `from_is_mailbox`, `address`, `messaging_account_id`), before any recipient is written or queued. This check runs FIRST, before the first-campaign platform approval gate: where mail leaves from is decided before whether the workspace may campaign.\n\nLegal from `draft` and `scheduled`; a 409 from anywhere else. Sending is not undoable once recipients start receiving — `/pause` stops the remainder, it does not recall what went out.\n\nThe response is the campaign plus `started: true` and `waiting_for_window_until`: a paced campaign (or a pilot) sent outside its send window sends nothing yet and waits for the window — this is when it opens; null = the first batch leaves now. With a `send_schedule` it is the first slot.\n\nOptional body `{ send_schedule }` (ADR 0139): sets the schedule and sends in one step. The «¿Lista para enviar?» checks (`GET /campaigns/{id}/preflight`) run again here: while any is `block` the answer is a **409 `preflight_blocked`** with the checks in `error.details.checks`, and nothing changes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignSendNowBody"
              },
              "example": {
                "send_schedule": {
                  "kind": "daily",
                  "timezone": "America/Santiago",
                  "start_date": "2026-10-01",
                  "time": "20:00"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The campaign, now scheduled, and whether it waits for its window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignSendNowResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "scheduled",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": "2026-10-01T15:00:00.000Z",
                    "started_at": null,
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 0,
                    "delivered_count": 0,
                    "read_count": 0,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`CAMPAIGN_FROM_CONNECTED_MAILBOX` — the email campaign would leave from a person's connected mailbox (ADR 0108 §Decision 10).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/preflight": {
      "get": {
        "summary": "«¿Lista para enviar?» — the checks before a send",
        "description": "Everything worth knowing before pressing send, as a check list (ADR 0139): each `{ key, status: ok | warn | block, title_es/_en, detail_es/_en?, href? }`. WhatsApp: `template_approved`, `recipients` (reachable vs not), `header_media`, `test_sent` (a test in the last 7 days), `messaging_tier`, `number_quality`, `schedule` (the simulation’s issues), `replies` (who answers the replies: the AI agent the number routes to, or the team — `warn` when the team answers and some sends fall outside the contact hours), `opt_out` (a quick-reply button to say no), `recent_campaign_overlap` (people who got a campaign in the last 7 days). Email: `template`, `recipients`, `unsubscribe`, `test_sent`, `sender_domain`. `ready` = no `block`; `/send` refuses with 409 `preflight_blocked` otherwise. `summary` says how it will go (recipients, schedule label, first batch, completion, batches) and `plan` is the schedule simulated («Ver plan»). Reads only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The checks, the summary and the plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignPreflight"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ready": true,
                    "checks": [
                      {
                        "key": "template_approved",
                        "status": "ok",
                        "title_es": "Plantilla aprobada por Meta",
                        "title_en": "Template approved by Meta",
                        "detail_es": "promo_octubre",
                        "detail_en": "promo_octubre"
                      },
                      {
                        "key": "recipients",
                        "status": "warn",
                        "title_es": "Se enviará a 801 personas",
                        "title_en": "It will go to 801 people",
                        "detail_es": "50 no la recibirán: sin número válido, duplicadas o pidieron no recibir mensajes.",
                        "detail_en": "50 will not get it: no valid number, duplicates or opted out.",
                        "href": "/campaigns/audiences/7a7a7a7a-0000-4000-8000-0000000000a1"
                      },
                      {
                        "key": "messaging_tier",
                        "status": "ok",
                        "title_es": "Tu número puede iniciar 250 conversaciones cada 24 h",
                        "title_en": "Your number can start 250 conversations every 24 h",
                        "detail_es": "Las campañas dejan 50 libres para recordatorios y otros mensajes.",
                        "detail_en": "Campaigns leave 50 free for reminders and other messages."
                      }
                    ],
                    "summary": {
                      "recipients": 801,
                      "channel": "whatsapp",
                      "template_name": "promo_octubre",
                      "schedule_label_es": "Todos los días a las 20:00 (America/Santiago) desde hoy",
                      "schedule_label_en": "Every day at 20:00 (America/Santiago) from today",
                      "first_batch_at": "2026-10-01T23:00:00.000Z",
                      "completes_at": "2026-10-05T23:00:00.000Z",
                      "batches_count": 5,
                      "limit_per_24h": 250,
                      "reserve": 50
                    },
                    "plan": {
                      "batches": [
                        {
                          "at": "2026-10-01T23:00:00.000Z",
                          "local": "jue 1 oct, 20:00",
                          "planned": 200,
                          "slot_index": null,
                          "warnings": []
                        }
                      ],
                      "covers_all": true,
                      "remaining_after_last": 0,
                      "completes_at": "2026-10-05T23:00:00.000Z",
                      "issues": [],
                      "tier_unknown": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/pause": {
      "post": {
        "summary": "Pause the campaign",
        "description": "Legal from `sending` and `scheduled`; a 409 otherwise. Stops further delivery — messages already sent are already sent. A person's pause reads `paused_reason: manual`. The platform also pauses on its own: `messaging_limit` (Meta pushed back; resumes by itself within the hour), `number_quality` (the WhatsApp number's quality turned RED; every sending or scheduled campaign on the number and its WABA siblings stops, and only a person resumes it), `budget_exhausted`, `delivery_guard` (WhatsApp: undeliverable 131026 + withheld 131049 failures passed 10% of the sends, after 50), `pilot`, `schedule_exhausted`, `config_error` and `reputation_guard` (email). Each automatic pause fires the `campaign.paused` webhook.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The paused campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "paused",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": "2026-10-01T15:00:00.000Z",
                    "finished_at": null,
                    "paused_reason": "manual",
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 120,
                    "delivered_count": 110,
                    "read_count": 64,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/resume": {
      "post": {
        "summary": "Resume a paused campaign",
        "description": "**Where it resumes to depends on whether it had started.** A campaign paused mid-send returns to `sending` and continues from where it got to; one paused before the recipient list was ever materialised returns to `scheduled` with the schedule reset to now. So the same call produces two different statuses, and neither is an error.\n\nAn optional body `{ send_mode }` switches how the rest is sent — e.g. an `asap` campaign the messaging-limit budget paused (`budget_exhausted`) resumed as `paced` continues by itself across the following days. A pilot (`paused_reason: pilot`) resumes into the full send.\n\n**A WhatsApp number whose quality is not GREEN asks for confirmation.** When the stored quality of the campaign's number is YELLOW or RED (or unknown after a `number_quality` pause), a resume without `acknowledge_quality: true` answers 409 with `details: { reason: \"number_quality_not_green\", quality_rating: \"RED\" | \"YELLOW\" | null }` and leaves it paused; with it, the campaign resumes. Nothing resumes a `number_quality` pause automatically.\n\nA 409 if the campaign is not paused. An EMAIL campaign is re-checked against the connected-mailbox rule before it resumes (a mailbox may have been connected, or the sending domain removed, while it was paused): a 422 `CAMPAIGN_FROM_CONNECTED_MAILBOX` leaves it paused.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignResumeBody"
              },
              "example": {
                "send_mode": "paced"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The resumed campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "sending",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": "2026-10-01T15:00:00.000Z",
                    "finished_at": null,
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 120,
                    "delivered_count": 110,
                    "read_count": 64,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T15:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "`CAMPAIGN_FROM_CONNECTED_MAILBOX` — the email campaign would leave from a person's connected mailbox (ADR 0108 §Decision 10).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/cancel": {
      "post": {
        "summary": "Cancel the campaign",
        "description": "Terminal, and the widest transition here: legal from `draft`, `scheduled`, `sending` and `paused`. There is no un-cancel — a cancelled campaign can only be deleted or left as a record. Recipients already delivered to are unaffected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "name": "Recordatorio de control",
                    "channel": "whatsapp",
                    "status": "cancelled",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "audience_id": "7a7a7a7a-0000-4000-8000-0000000000a1",
                    "messaging_account_id": "7a7a7a7a-0000-4000-8000-0000000000b1",
                    "template_id": "7a7a7a7a-0000-4000-8000-0000000000d1",
                    "template_params": {
                      "1": {
                        "source": "contact_field",
                        "field": "first_name",
                        "fallback": "cliente"
                      },
                      "2": {
                        "source": "static",
                        "value": "15 de noviembre"
                      }
                    },
                    "content": {},
                    "reply_mode": "account_default",
                    "scheduled_at": null,
                    "started_at": "2026-10-01T15:00:00.000Z",
                    "finished_at": "2026-10-01T15:50:00.000Z",
                    "paused_reason": null,
                    "send_mode": "paced",
                    "pilot_size": null,
                    "pilot_completed_at": null,
                    "pacing_next_batch_at": null,
                    "total_recipients": 500,
                    "suppressed_count": 12,
                    "sent_count": 120,
                    "delivered_count": 110,
                    "read_count": 64,
                    "failed_count": 0,
                    "replied_count": 0,
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T15:50:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/recipients": {
      "get": {
        "summary": "List the campaign’s recipients",
        "description": "One row per recipient with its delivery state — `pending`, `sent`, `delivered`, `read`, `failed` or `suppressed`. `suppressed` means the contact was in the audience but the suppression list stopped the send, which is a successful outcome rather than a failure.\n\nThe list only exists once the run materialises it, so a draft or not-yet-started campaign answers empty rather than previewing who WOULD receive it. `limit` defaults to 50 and caps at 500; the count is `meta.pagination.total`.\n\nFilters: `state`, `error_code` (Meta's code, failed rows of one reason) and `replied`. Each row carries Meta's raw `error`, the same failure in plain language with a suggested action (`error_reason`), and `replied_button` — the template quick-reply button the person's first reply tapped (null for free text or no reply).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sent",
                "delivered",
                "read",
                "failed",
                "suppressed"
              ]
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40,
              "description": "Meta's error code (e.g. `131026`) — failed rows of one reason."
            },
            "required": false,
            "description": "Meta's error code (e.g. `131026`) — failed rows of one reason.",
            "name": "error_code",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`true`: only recipients who replied; `false`: only those who did not."
            },
            "required": false,
            "description": "`true`: only recipients who replied; `false`: only those who did not.",
            "name": "replied",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Recipients and their states",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CampaignRecipient"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000101",
                      "campaign_id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                      "contact_id": "7a7a7a7a-0000-4000-8000-0000000000f1",
                      "identifier": "+56912345678",
                      "params": {
                        "1": "María",
                        "2": "15 de noviembre"
                      },
                      "rendered_text": null,
                      "state": "pending",
                      "external_message_id": null,
                      "error": null,
                      "sent_at": null,
                      "replied_at": null,
                      "replied_conversation_id": null,
                      "created_at": "2026-10-01T15:00:00.000Z"
                    },
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000102",
                      "campaign_id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                      "contact_id": "7a7a7a7a-0000-4000-8000-0000000000f2",
                      "identifier": "+56987654321",
                      "params": {
                        "1": "cliente",
                        "2": "15 de noviembre"
                      },
                      "rendered_text": null,
                      "state": "failed",
                      "external_message_id": null,
                      "error": {
                        "code": "131026",
                        "title": "Message undeliverable",
                        "detail": null
                      },
                      "error_reason": {
                        "code": "131026",
                        "human_es": "El número no tiene WhatsApp o no puede recibir este mensaje (app desactualizada, no aceptó los términos o bloqueó a la empresa).",
                        "human_en": "The number is not on WhatsApp or cannot receive this message (outdated app, terms not accepted, or it blocked the business).",
                        "action": "remove_number"
                      },
                      "replied_button": null,
                      "sent_at": null,
                      "replied_at": null,
                      "replied_conversation_id": null,
                      "created_at": "2026-10-01T15:00:00.000Z"
                    },
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000103",
                      "campaign_id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                      "contact_id": null,
                      "identifier": "+56955500000",
                      "params": {},
                      "rendered_text": null,
                      "state": "suppressed",
                      "external_message_id": null,
                      "error": null,
                      "sent_at": null,
                      "replied_at": null,
                      "replied_conversation_id": null,
                      "created_at": "2026-10-01T15:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 500,
                      "limit": 50
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/recipients/export.csv": {
      "get": {
        "summary": "Export the campaign’s recipients as CSV",
        "description": "The whole recipients list (same filters as the list: `state`, `error_code`, `replied`) as a UTF-8 CSV with a BOM and Spanish headers: `Nombre, Identificador, Estado, Enviado, Entregado, Leído, Respondió, Botón, Código de error, Motivo, Acción sugerida`. Times are in the workspace time zone. Requires `campaigns:read` AND `contacts:read` (it carries names and phone numbers).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "sent",
                "delivered",
                "read",
                "failed",
                "suppressed"
              ]
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 40,
              "description": "Meta's error code (e.g. `131026`) — failed rows of one reason."
            },
            "required": false,
            "description": "Meta's error code (e.g. `131026`) — failed rows of one reason.",
            "name": "error_code",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`true`: only recipients who replied; `false`: only those who did not."
            },
            "required": false,
            "description": "`true`: only recipients who replied; `false`: only those who did not.",
            "name": "replied",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The CSV file",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/results": {
      "get": {
        "summary": "The campaign’s results",
        "description": "What happened to the campaign, from its own recipients: the funnel (`recipients → sent → delivered → read → replied`, plus `failed`, `opted_out`, `suppressed`, `pending`) and its rates (delivered/sent, read/delivered, replied/delivered); replies per template quick-reply button (a reply whose first message is a button label) and the `free_text` rest; failures grouped by Meta error code, each with a plain-language reason (es/en) and a suggested `action`; opt-outs attributable to the campaign (recorded by it, or on a recipient’s number within the reply-attribution window after the send) by reason; and a timeline of sends, deliveries, reads and replies per hour (per day past a week), bucketed in the workspace time zone. Delivery/read times are recorded from 2026-10-14 on; older sends show in the funnel but not in the timeline.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignResults"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "campaign_id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "channel": "whatsapp",
                    "funnel": {
                      "recipients": 64,
                      "sent": 64,
                      "delivered": 52,
                      "read": 40,
                      "replied": 17,
                      "opted_out": 2,
                      "failed": 9,
                      "suppressed": 0,
                      "pending": 0
                    },
                    "rates": {
                      "delivered": 0.8125,
                      "read": 0.7692,
                      "replied": 0.3269
                    },
                    "buttons": [
                      {
                        "text": "Quiero más información",
                        "count": 10
                      },
                      {
                        "text": "No me interesa",
                        "count": 2
                      }
                    ],
                    "free_text": 5,
                    "failures": [
                      {
                        "code": "131026",
                        "title": "Message undeliverable",
                        "human_es": "El número no tiene WhatsApp o no puede recibir este mensaje (app desactualizada, no aceptó los términos o bloqueó a la empresa).",
                        "human_en": "The number is not on WhatsApp or cannot receive this message (outdated app, terms not accepted, or it blocked the business).",
                        "action": "remove_number",
                        "count": 4
                      },
                      {
                        "code": "130472",
                        "title": "User's number is part of an experiment",
                        "human_es": "Meta retiene mensajes de marketing a una muestra de usuarios para un experimento. No depende de ti.",
                        "human_en": "Meta holds back marketing messages from a sample of users for an experiment. Nothing on your side.",
                        "action": "none",
                        "count": 2
                      }
                    ],
                    "opt_outs": {
                      "count": 2,
                      "by_reason": {
                        "stop_keyword": 1,
                        "classified_marketing": 1
                      }
                    },
                    "timeline": {
                      "unit": "hour",
                      "timezone": "America/Santiago",
                      "points": [
                        {
                          "at": "2026-09-15T17:00:00.000Z",
                          "sent": 64,
                          "delivered": 52,
                          "read": 10,
                          "replied": 4
                        },
                        {
                          "at": "2026-09-15T18:00:00.000Z",
                          "sent": 0,
                          "delivered": 0,
                          "read": 4,
                          "replied": 2
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/meta-insights": {
      "get": {
        "summary": "Meta’s own analytics for the campaign’s template",
        "description": "Meta’s per-template analytics — sent, delivered, read, clicks per button (quick-reply AND URL buttons) and cost — for the campaign’s WhatsApp template over its days (`started_at` → `finished_at` or now, padded to whole UTC days, at most the last 90), read through the messaging bridge (ADR 0137) and cached 15 minutes. These are the TEMPLATE’s numbers over those days: another campaign sending the same template on the same days shares them.\n\n**202** `{ data: { status: \"enabling\", retry_after_seconds } }` the first time, while Meta switches the account’s template insights on — ask again after `retry_after_seconds`. 409 when the campaign has not started; 422 for a campaign that is not a WhatsApp template campaign; 404 when the bridge does not know the template; 429 (with `retry_after_seconds`) when Meta throttles; 502 when Meta is unavailable; 501 `meta_insights_unavailable` when Meta’s numbers cannot exist for the account: `reason` `not_on_bridge` (not connected through the bridge) or `coexistence` (a WhatsApp Business app number — Meta refuses template insights there).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Meta’s template analytics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignMetaInsights"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "status": "ready",
                    "template": {
                      "name": "promo_octubre",
                      "language": "es",
                      "external_id": "1234567890"
                    },
                    "range": {
                      "start": "2026-10-01",
                      "end": "2026-10-14"
                    },
                    "totals": {
                      "sent": 820,
                      "delivered": 790,
                      "read": 512,
                      "cost": {
                        "amount": "41.2",
                        "currency": "USD"
                      }
                    },
                    "clicks": [
                      {
                        "type": "quick_reply_button",
                        "button": "Quiero más información",
                        "count": 61
                      },
                      {
                        "type": "url_button",
                        "button": "Ver promoción",
                        "count": 37
                      }
                    ],
                    "daily": [
                      {
                        "date": "2026-10-13",
                        "sent": 400,
                        "delivered": 388,
                        "read": 250,
                        "clicks": 52
                      }
                    ],
                    "fetched_at": "2026-10-14T13:05:00.000Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Meta is switching the template insights on — retry later",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignMetaInsights"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "501": {
            "description": "`meta_insights_unavailable` — the account is not connected through the bridge.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/campaigns/{id}/test-send": {
      "post": {
        "summary": "Send a test of the campaign to up to five people",
        "description": "Sends the campaign, exactly as it will leave, to 1–5 recipients typed by hand — `phone` for a WhatsApp campaign, `email` for an email one — and answers one result per recipient: `sent` (with `provider_message_id`) or `failed` (with `error_code`). It writes NO recipient rows and does not touch the campaign’s status or counters, so it is safe on a scheduled, paused or sent campaign and leaves no trace in the stats; a `sending` or `cancelled` campaign answers 409 `campaign_not_testable`.\n\n**Readiness** is the real send’s, minus the audience: a WhatsApp test needs the template APPROVED on the campaign’s connected account and a resolvable media header (400 `header_media_missing` otherwise); an email test needs content and a verified sending domain.\n\n**Values.** Per template slot: `variables[slot]` when typed → else the campaign mapping read with the typed recipient as the contact (a `contact_field` slot gets the typed `name` / `phone` / `email`, else the mapping’s `fallback`; a `static` slot its text) → else the template’s example. A slot still empty refuses that recipient (`missing_variables`). The media header is always the campaign’s own (`template_params.header_media`) else the template default — never a test variable. Each result echoes `values` with the `source` of each.\n\n**WhatsApp tests are conversations.** The number’s contact and its active thread on the campaign’s account are found or created, and the message is stored there (`metadata.campaign_test: true`), so a reply reaches the AI agent with the campaign in context — and is never attributed to the campaign’s metrics. `conversation_id` points at it.\n\n**Refusals per recipient:** `invalid_phone`, `landline`, `invalid_email`, `duplicate`, `opted_out` (with `opt_out_reason`), `contact_blocked`, `safety_hold`, `missing_variables`, `email_not_verified` (SES sandbox: only verified addresses), `send_failed` (with `provider_code`).\n\n**Rate limit:** 20 test messages per campaign and 60 per workspace per hour, counted for the recipients actually sent to; past it the whole request is refused with 429 `RATE_LIMITED` (`details.retry_after_s`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Campaigns"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignTestSendBody"
              },
              "example": {
                "recipients": [
                  {
                    "phone": "+56 9 1234 5678",
                    "name": "María González",
                    "variables": {
                      "1": "María"
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per recipient",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CampaignTestSendResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "campaign_id": "7a7a7a7a-0000-4000-8000-0000000000c1",
                    "channel": "whatsapp",
                    "header_media_url": null,
                    "remaining_this_hour": 4,
                    "results": [
                      {
                        "phone": "+56912345678",
                        "name": "María González",
                        "status": "sent",
                        "conversation_id": "7a7a7a7a-0000-4000-8000-000000000201",
                        "contact_id": "7a7a7a7a-0000-4000-8000-0000000000f1",
                        "rendered_text": "Hola María, tu próximo control es el 15 de noviembre.",
                        "values": {
                          "1": {
                            "value": "María",
                            "source": "variable"
                          },
                          "2": {
                            "value": "15 de noviembre",
                            "source": "static"
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/feed": {
      "get": {
        "summary": "Agenda for a window, any engine, by professional and branch",
        "description": "Mirrored AND native appointments between `from` (inclusive) and `to` (EXCLUSIVE), bare `YYYY-MM-DD` days in the clinic's timezone; the window may not exceed 8 days. Cancelled citas are INCLUDED (the grid draws them muted). Unlike `GET /clinic/agenda`, `professional_id` and `location_id` are VITRINA uuids — `clinic_professional.id` and `location.id` — so one query serves a Medilink, a Reservo and a native clinic. Each row still carries the vendor's own ids under `vendor` for provenance: a row with `vendor.professional_id` set and `professional: null` is a roster gap, never a cita nobody is attending. Never calls the clinic API. `flags` are the clinical alerts the clinic shows on the block: clinical-record content, returned to an API key or personal token only when it also holds `clinic_record:read`. `booking_kind` is the cita's Tipo de agendamiento: `new` (Nuevo: it would be a primera visita and the patient never booked before), `new_rebooked` (Nuevo · reagenda: a primera visita, but the patient had earlier bookings and never came) or `in_treatment` (En tratamiento: every other cita); null on a row that is not a patient's cita. It uses the primera-visita rule Vitrina Ads counts, so a patient back after 12 months without an attended cita is `new`. `origin` is the patient's Origen (`kind` `meta` with the ad, ad set and campaign names, or `organic`) — the attribution Vitrina Ads reports, one definition; null on a row that is not a patient's cita. Filter with `origin=meta|organic|unknown` and, for Meta, `origin_ad_id` (a Meta ad id): rows that are not a patient's cita never match. `is_diagnosis` marks a diagnosis cita — a primera visita (`booking_kind` `new` / `new_rebooked`), an entry prestación the clinic confirmed, or a cita the clinic named as an evaluación / diagnóstico (the prestación, or the vendor's tratamiento label) unless its prestación is confirmed not to be an entry; `diagnosis=true` returns only those. `booked_via` says how the cita was booked: `agent` (Vitrina's AI agent, with the conversation `channel`), `online` (the patient on a booking page), `whatsapp` (a person booked it within 48 h of a WhatsApp exchange with the patient), `staff` (a team member in Vitrina, `by`), `vendor` (keyed straight into the clinic's system) or `calendar`; `booked_at` is the vendor's own creation time when known. `patient_paid` is what the patient paid on their plans, all time (espejo de ingresos abonos plus presupuesto allocations), returned only to a caller holding `clinic_money:read`. `owed_clp` is everything the cita still owes (abono AND performed prestaciones: every open receivable on the cita, net of allocations) and `patient_owed_clp` what its patient owes that is due by today (no deadline, or a deadline on or before today — a future cuota is not owed yet); both null without `clinic_money:read`. `money` stays the abono alone. `unconfirmed` is true on a live cita (`status` `confirmed`) whose clinic status says the patient has not confirmed it yet («No confirmado»): draw it as «Sin confirmar». It is a confirmation flag beside `status`, never a lifecycle state — the cita still counts as booked everywhere `status` `confirmed` does.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "resource_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pending_hold",
                  "confirmed",
                  "cancelled",
                  "completed",
                  "no_show"
                ]
              },
              "minItems": 1
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "test_drive",
                  "external",
                  "block",
                  "clinic"
                ]
              },
              "minItems": 1
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads."
            },
            "required": false,
            "description": "Origen of the patient: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no linked contact). Same attribution as Vitrina Ads' people from ads.",
            "name": "origin",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "Narrow `origin=meta` to one Meta ad (its platform ad id)."
            },
            "required": false,
            "description": "Narrow `origin=meta` to one Meta ad (its platform ad id).",
            "name": "origin_ad_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "diagnosis",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Agenda feed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000001",
                      "display_id": "A-1",
                      "engine": "native",
                      "external_id": null,
                      "kind": "clinic",
                      "status": "confirmed",
                      "unconfirmed": false,
                      "clinic_status_id": null,
                      "status_label": null,
                      "status_color": null,
                      "starts_at": "2026-09-23T13:30:00.000Z",
                      "ends_at": "2026-09-23T14:00:00.000Z",
                      "arrival_state": "scheduled",
                      "professional": {
                        "id": "ffffffff-0000-4000-8000-000000000001",
                        "name": "Ana"
                      },
                      "location": {
                        "id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "name": "Sucursal Maipú"
                      },
                      "resource": null,
                      "service": null,
                      "patient": {
                        "id": "12121212-0000-4000-8000-000000000001",
                        "name": "María José Fuentes Lagos",
                        "external_id": null
                      },
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "María José Fuentes Lagos"
                      },
                      "customer_name": "María José Fuentes Lagos",
                      "notes": "Avisar al +56987654321 si se atrasa",
                      "money": null,
                      "booking_kind": "new",
                      "is_diagnosis": true,
                      "booked_via": {
                        "kind": "agent",
                        "channel": "whatsapp",
                        "by": null,
                        "booked_at": "2026-09-21T18:04:00.000Z"
                      },
                      "patient_paid": 250000,
                      "calendar_mirror_status": "synced",
                      "calendar_mirror_error": null,
                      "calendar_mirror_attempted_at": "2026-09-21T18:04:02.000Z",
                      "flags": [
                        {
                          "id": "13131313-0000-4000-8000-000000000001",
                          "kind": "alergia",
                          "label": "Alergia a penicilina",
                          "severity": "severa"
                        }
                      ],
                      "vendor": {
                        "professional_id": null,
                        "professional_name": null,
                        "sucursal_id": null,
                        "agenda_id": null,
                        "sillon": null,
                        "status_code": null
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/diagnosticos": {
      "get": {
        "summary": "Diagnósticos: one day of diagnosis citas with plans and abono",
        "description": "The closing routine's worklist. `date` (`YYYY-MM-DD`, the clinic's clock; default today) returns that day's diagnosis citas in time order — the same `is_diagnosis` rule as `GET /clinic/agenda/feed` — each with its attendance (`attended`, `no_show`, `cancelled`, `booked`), professional, Tipo de agendamiento and Origen, and the patient's plans of the last 12 months: from the espejo de ingresos (the vendor's plan total and abonado) for a Dentalink clinic, from its presupuestos (total and what was allocated to them) for a native one; plans of $0 are not plans. At most 3 plans per patient are listed, `plans_count`, `plan_total` and `paid_total` cover all of them. `summary` counts the day (cancelled citas are counted apart and never as Meta) and `upcoming` the next 5 working days (a Sunday appears only when it holds a diagnosis cita). Plans are money: without `clinic_money:read` every `money` is null and `money_withheld` is true; `plan_created_on_day` stays. Never calls the clinic API.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "date",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Diagnosis day",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "date": "2026-09-23",
                    "summary": {
                      "total": 1,
                      "attended": 1,
                      "no_show": 0,
                      "cancelled": 0,
                      "meta": 0,
                      "plan_created_on_day": 1
                    },
                    "rows": [
                      {
                        "appointment_id": "eeeeeeee-0000-4000-8000-000000000001",
                        "display_id": "A-1",
                        "starts_at": "2026-09-23T13:30:00.000Z",
                        "ends_at": "2026-09-23T14:00:00.000Z",
                        "status": "completed",
                        "status_label": "Atendido",
                        "attendance": "attended",
                        "patient": {
                          "id": "12121212-0000-4000-8000-000000000001",
                          "name": "María José Fuentes Lagos",
                          "external_id": null
                        },
                        "professional": {
                          "id": "ffffffff-0000-4000-8000-000000000001",
                          "name": "Ana"
                        },
                        "service": null,
                        "location": {
                          "id": "b1b1b1b1-0000-4000-8000-000000000002",
                          "name": "Sucursal Maipú"
                        },
                        "booking_kind": "new",
                        "origin": {
                          "kind": "organic",
                          "label": "Orgánico",
                          "ad_id": null,
                          "ad_name": null,
                          "ad_set_name": null,
                          "campaign_name": null,
                          "first_touch_at": null,
                          "via": null
                        },
                        "external_id": null,
                        "engine": "native",
                        "money": {
                          "plans": [
                            {
                              "source": "native",
                              "id": "eeeeeeee-0000-4000-8000-000000000002",
                              "label": "E-12",
                              "day": "2026-09-23",
                              "total": 1250000,
                              "paid": 250000,
                              "status": "accepted",
                              "professional_name": "Ana Rojas",
                              "created_on_day": true
                            }
                          ],
                          "plans_count": 1,
                          "plan_total": 1250000,
                          "paid_total": 250000
                        },
                        "plan_created_on_day": true
                      }
                    ],
                    "upcoming": [
                      {
                        "date": "2026-09-24",
                        "count": 4
                      },
                      {
                        "date": "2026-09-25",
                        "count": 3
                      },
                      {
                        "date": "2026-09-26",
                        "count": 1
                      },
                      {
                        "date": "2026-09-28",
                        "count": 5
                      },
                      {
                        "date": "2026-09-29",
                        "count": 2
                      }
                    ],
                    "money_withheld": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/month": {
      "get": {
        "summary": "Agenda month: citas per day, by Origen",
        "description": "Counts per day of one `month` (`YYYY-MM`, the clinic's clock), read in one pass: `citas` (patients' citas that still stand), split by Origen (`meta`, `organic`, `unknown` — the same resolver as the feed and Vitrina Ads), `diagnosis` (diagnosis citas among them, the feed's `is_diagnosis` rule) and `cancelled` (anuladas, counted apart). Blocks and the workspace's own events never count. Optional `location_id` / `professional_id` (Vitrina uuids). Never calls the clinic API.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": true,
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Agenda month",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "days": [
                      {
                        "date": "2026-10-01",
                        "citas": 42,
                        "meta": 9,
                        "organic": 20,
                        "unknown": 13,
                        "diagnosis": 7,
                        "cancelled": 4
                      },
                      {
                        "date": "2026-10-02",
                        "citas": 38,
                        "meta": 6,
                        "organic": 19,
                        "unknown": 13,
                        "diagnosis": 5,
                        "cancelled": 2
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/free-hours": {
      "get": {
        "summary": "Horas libres: one day of free hours per professional",
        "description": "Every free slot of `date` from the clinic's own booking engine (the same read as `GET /clinic/agenda/availability`, whole day), grouped per professional with back-to-back slots merged into `ranges` and `slots` = how many cupos. `especialidad` narrows to the professionals whose specialties or note name it; `especialidades` lists the roster's own, for a picker. `professional_id` is the Vitrina uuid when the roster knows them. For a Dentalink / Medilink clinic this is one live vendor request per call (rate-limited upstream), so read it on demand rather than on a timer. A past day has no free hours.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": true,
            "name": "date",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            },
            "required": false,
            "name": "especialidad",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Free hours",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "date": "2026-09-24",
                    "engine": "native-clinic",
                    "timezone": "America/Santiago",
                    "especialidad": null,
                    "professionals": [
                      {
                        "professional_id": "ffffffff-0000-4000-8000-000000000001",
                        "engine_professional_id": "ffffffff-0000-4000-8000-000000000001",
                        "name": "Ana Rojas",
                        "especialidad": "Implantología",
                        "ranges": [
                          {
                            "starts_at": "2026-09-24T12:00:00.000Z",
                            "ends_at": "2026-09-24T13:30:00.000Z"
                          },
                          {
                            "starts_at": "2026-09-24T17:00:00.000Z",
                            "ends_at": "2026-09-24T18:00:00.000Z"
                          }
                        ],
                        "slots": 5
                      }
                    ],
                    "especialidades": [
                      "Endodoncia",
                      "Implantología",
                      "Ortodoncia"
                    ],
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/availability": {
      "get": {
        "summary": "Free slots, from whichever engine owns this diary",
        "description": "One endpoint, three truths: a Medilink clinic gets its vendor's live answer, a Reservo clinic the local compute over its mirror, and a native clinic the professionals' work patterns minus exceptions, existing citas and room contention. Each slot carries an opaque `slot_ref` that pins the professional, the box and the branch — book it back verbatim. `allow_overbook` is a STAFF flag and defaults to false: an hour that only exists because the clinic permits sobrecupo is a receptionist's decision with a reason, never something a patient-facing read gives away. `professional_id` is the `id` returned by clinic_professionals_list, and each slot's `professional_id` is in that same id space. The window is half-open like every agenda read: `from` inclusive, `to` EXCLUSIVE. `truncated: true` means more free slots exist than were listed — days of the window left unread (see `searched_through`) or the `limit` cap (max 100) reached; narrow by `professional_id` / `especialidad` or shorten the window to see the rest.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "service_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One professional's agenda: the `id` returned by clinic_professionals_list (GET /clinic/professionals) — a Vitrina uuid, never the clinic system's own professional id. An inactive or unknown id is refused with `unknown_professional`, never widened to the whole roster."
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "especialidad",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day read (`YYYY-MM-DD`, clinic clock), inclusive. Default today; a past day reads from today unless `include_past` is set."
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Day the window ends BEFORE (`YYYY-MM-DD`) — EXCLUSIVE, like every other agenda read: `from=2026-10-14&to=2026-10-15` is the 14th only. Omit for an open-ended forward scan."
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "allow_overbook",
            "in": "query"
          },
          {
            "schema": {
              "type": "number",
              "description": "STAFF: size every slot to this many minutes instead of the visit type's length («60 min · cambiar»). Whole minutes, 5–480 in steps of 5. Native diary only; Dentalink/Medilink/Reservo ignore it."
            },
            "required": false,
            "name": "duration_minutes",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ],
              "description": "STAFF: read a past `from` day (and today's earlier hours) as they were, instead of from now — to register a visit that already happened. Booking one of those hours still needs `confirm_past: true`. Native diary only; Dentalink/Medilink/Reservo ignore it."
            },
            "required": false,
            "name": "include_past",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ],
              "description": "STAFF: every free slot of the explicit `from`–`to` window (at most 7 days), every professional, in ONE read — no `limit` cap and no `truncated` from the cap. «Nueva cita» reads a whole day this way instead of one request per professional. Needs both `from` and `to`; `limit` is ignored."
            },
            "required": false,
            "name": "complete",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Availability",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "engine": "native-clinic",
                    "timezone": "America/Santiago",
                    "slots": [
                      {
                        "starts_at": "2026-09-24T12:00:00.000Z",
                        "ends_at": "2026-09-24T12:30:00.000Z",
                        "label": "jueves, 24 de septiembre, 09:00",
                        "slot_ref": "ncl1_eyJwIjoiZmZmZmZmZmYtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwibCI6ImIxYjFiMWIxLTAwMDAtNDAwMC04MDAwLTAwMDAwMDAwMDAwMiIsInMiOiIyMDI2LTA5LTI0VDEyOjAwOjAwLjAwMFoiLCJlIjoiMjAyNi0wOS0yNFQxMjozMDowMC4wMDBaIn0",
                        "professional_id": "ffffffff-0000-4000-8000-000000000001",
                        "professional_name": "Ana Rojas"
                      },
                      {
                        "starts_at": "2026-09-24T12:30:00.000Z",
                        "ends_at": "2026-09-24T13:00:00.000Z",
                        "label": "jueves, 24 de septiembre, 09:30",
                        "slot_ref": "ncl1_eyJwIjoiZmZmZmZmZmYtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAxIiwibCI6ImIxYjFiMWIxLTAwMDAtNDAwMC04MDAwLTAwMDAwMDAwMDAwMiIsInMiOiIyMDI2LTA5LTI0VDEyOjMwOjAwLjAwMFoiLCJlIjoiMjAyNi0wOS0yNFQxMzowMDowMC4wMDBaIn0",
                        "professional_id": "ffffffff-0000-4000-8000-000000000001",
                        "professional_name": "Ana Rojas"
                      }
                    ],
                    "searched_through": "2026-09-24",
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/open-plans": {
      "get": {
        "summary": "A patient's open treatment plans, for booking a cita into one",
        "description": "The patient's plans de tratamiento that are not closed (draft, proposed, accepted, in progress), newest first, each with its professional. `suggested_plan_id` is the plan a booking with `professional_id` joins when no plan is named: the newest open plan with that professional; null means the booking opens a new one. Same shape as the Dentalink/Medilink read, so one plan step serves every engine. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The paciente (`clinic_patient.id`) the cita is for."
            },
            "required": true,
            "name": "patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cita's professional: `suggested_plan_id` is the patient's newest open plan with them."
            },
            "required": false,
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Open plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "paciente_id": "5b0f8a54-3a43-4c1a-9a51-0e7d3c2f9a10",
                  "plans": [
                    {
                      "id": "9d1c2b3a-4e5f-4a6b-8c7d-1e2f3a4b5c6d",
                      "nombre": "Ortodoncia",
                      "profesional_id": "0a1b2c3d-4e5f-4a6b-9c8d-7e6f5a4b3c2d",
                      "profesional_nombre": "Diego Araya",
                      "fecha": "2026-09-14",
                      "total": null,
                      "deuda": null
                    }
                  ],
                  "suggested_plan_id": "9d1c2b3a-4e5f-4a6b-8c7d-1e2f3a4b5c6d"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/agenda/appointments": {
      "post": {
        "summary": "Book a cita in the diary Vitrina owns",
        "description": "Routes through the tenant's booking engine, so a vendor clinic gets its own engine's honest refusal rather than a local row the vendor never hears about. Pass either the `slot_ref` from `GET /clinic/agenda/availability` or a `professional_id` outright (a drag-to-create on the grid has no ref). One transaction: the cita, its prestación line with the duration and price FROZEN, and — when a deposit policy applies — the `customer_obligation` the ledger settles against. A collision is refused with `slot_taken` and fresh alternatives unless `allow_overbook` is set, which also requires a reason: a sobrecupo nobody can explain is indistinguishable from a double booking. On a native clinic the cita joins a plan de tratamiento the way Dentalink does it: the patient's newest open plan with the same professional, else a new plan «Diagnóstico» (`treatment_plan_id` on the row); pass `treatment_plan_id` to book into a plan of your choice (the patient's own, not closed — else `invalid_treatment_plan`), or `new_treatment_plan: true` to open a new plan named after the visit type even when an open one exists. `duration_minutes` (staff) books a length other than the visit type's: the cita ends `starts_at` + it. A prestación with eligible professionals set refuses anyone else (`professional_not_eligible`). Requires `clinic:write`, which no connected app holds by default.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slot_ref": {
                    "type": "string",
                    "minLength": 1
                  },
                  "professional_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "resource_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "service_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "customer_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "starts_at": {
                    "type": "string"
                  },
                  "ends_at": {
                    "type": "string"
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "allow_overbook": {
                    "type": "boolean"
                  },
                  "additional": {
                    "type": "boolean"
                  },
                  "overbook_reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300
                  },
                  "treatment_plan_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "new_treatment_plan": {
                    "type": "boolean"
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 480
                  },
                  "confirm_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "starts_at",
                  "ends_at"
                ],
                "additionalProperties": false
              },
              "example": {
                "professional_id": "ffffffff-0000-4000-8000-000000000001",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                "patient_id": "12121212-0000-4000-8000-000000000001",
                "starts_at": "2026-09-24T14:00:00.000Z",
                "ends_at": "2026-09-24T14:30:00.000Z",
                "notes": "Control mensual"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Appointment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "contact_id": null,
                    "conversation_id": null,
                    "vehicle_id": null,
                    "appointment_type_id": null,
                    "owner_user_id": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "professional_id": "ffffffff-0000-4000-8000-000000000001",
                    "resource_id": null,
                    "kind": "clinic",
                    "source": "agent",
                    "engine": "native",
                    "external_id": null,
                    "status": "confirmed",
                    "status_epoch": 0,
                    "clinic_status_id": null,
                    "arrival_state": "scheduled",
                    "arrived_at": null,
                    "in_room_at": null,
                    "done_at": null,
                    "series_id": null,
                    "series_index": null,
                    "treatment_plan_id": null,
                    "booking_landing_id": null,
                    "attribution": null,
                    "starts_at": "2026-09-24T14:00:00.000Z",
                    "ends_at": "2026-09-24T14:30:00.000Z",
                    "hold_expires_at": null,
                    "google_event_id": null,
                    "google_ical_uid": null,
                    "google_updated_at": null,
                    "google_etag": null,
                    "calendar_mirror_status": null,
                    "calendar_mirror_op": null,
                    "calendar_mirror_error": null,
                    "calendar_mirror_attempts": 0,
                    "calendar_mirror_attempted_at": null,
                    "calendar_mirror_next_retry_at": null,
                    "calendar_mirror_calendar_id": null,
                    "reminder_at": null,
                    "reminder_sent_at": null,
                    "reminder_job_id": null,
                    "customer_name": "María José Fuentes Lagos",
                    "notes": "Control mensual",
                    "metadata": {
                      "engine": "native-clinic",
                      "patient_rut": "11.111.111-1",
                      "clinic_patient_id": "12121212-0000-4000-8000-000000000001"
                    },
                    "display_seq": 2,
                    "display_id": "A-2",
                    "created_at": "2026-09-22T17:18:58.202Z",
                    "updated_at": "2026-09-22T17:18:58.202Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/agenda/appointments/{id}": {
      "patch": {
        "summary": "Move a cita",
        "description": "Reschedule through the owning engine — a native cita moves inside one transaction under the same per-professional lock a booking takes, and a mirrored one moves the way its vendor moves citas. The deposit's deadline travels with it: a cita moved a week out with a deadline that stayed put is a \"vencido\" nobody can date. `professional_id` / `resource_id` move the cita to another agenda or room in the same stroke; omitted, it keeps the ones it has. Requires `clinic:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slot_ref": {
                    "type": "string",
                    "minLength": 1
                  },
                  "professional_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "resource_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "starts_at": {
                    "type": "string"
                  },
                  "ends_at": {
                    "type": "string"
                  },
                  "allow_overbook": {
                    "type": "boolean"
                  },
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 480
                  },
                  "confirm_past": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "starts_at",
                  "ends_at"
                ],
                "additionalProperties": false
              },
              "example": {
                "starts_at": "2026-09-24T14:30:00.000Z",
                "ends_at": "2026-09-24T15:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Appointment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "contact_id": null,
                    "conversation_id": null,
                    "vehicle_id": null,
                    "appointment_type_id": null,
                    "owner_user_id": null,
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "professional_id": "ffffffff-0000-4000-8000-000000000001",
                    "resource_id": null,
                    "kind": "clinic",
                    "source": "agent",
                    "engine": "native",
                    "external_id": null,
                    "status": "confirmed",
                    "status_epoch": 0,
                    "clinic_status_id": null,
                    "arrival_state": "scheduled",
                    "arrived_at": null,
                    "in_room_at": null,
                    "done_at": null,
                    "series_id": null,
                    "series_index": null,
                    "treatment_plan_id": null,
                    "booking_landing_id": null,
                    "attribution": null,
                    "starts_at": "2026-09-24T14:30:00.000Z",
                    "ends_at": "2026-09-24T15:00:00.000Z",
                    "hold_expires_at": null,
                    "google_event_id": null,
                    "google_ical_uid": null,
                    "google_updated_at": null,
                    "google_etag": null,
                    "calendar_mirror_status": null,
                    "calendar_mirror_op": null,
                    "calendar_mirror_error": null,
                    "calendar_mirror_attempts": 0,
                    "calendar_mirror_attempted_at": null,
                    "calendar_mirror_next_retry_at": null,
                    "calendar_mirror_calendar_id": null,
                    "reminder_at": null,
                    "reminder_sent_at": null,
                    "reminder_job_id": null,
                    "customer_name": "María José Fuentes Lagos",
                    "notes": "Control mensual",
                    "metadata": {
                      "engine": "native-clinic",
                      "patient_rut": "11.111.111-1",
                      "clinic_patient_id": "12121212-0000-4000-8000-000000000001"
                    },
                    "display_seq": 2,
                    "display_id": "A-2",
                    "created_at": "2026-09-22T17:18:58.202Z",
                    "updated_at": "2026-09-22T17:19:15.981Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/migration/runs": {
      "get": {
        "summary": "The clinic’s migration runs, newest first",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "MigrationRuns",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "01929a5e-7c10-7d3e-9a41-5b8f2c6d1e07",
                        "mode": "apply",
                        "status": "completed",
                        "status_label": "Completada",
                        "families": [
                          "patients",
                          "appointments"
                        ],
                        "started_at": "2026-10-30T23:05:00.000Z",
                        "finished_at": "2026-10-31T01:42:10.000Z",
                        "totals": {
                          "examined": 4213,
                          "created": 3980,
                          "unchanged": 190,
                          "gaps": 43
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "The clinic’s migration runs, newest first\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/migration/runs/{id}": {
      "get": {
        "summary": "One migration run: progress per family, gaps by reason",
        "description": "Per family: status, phase, counts (examined / created / converted / updated / unchanged / kept_native / collapsed / skipped / gaps), progress against the latest dry-run, vendor requests. The gap summary counts what could not come across by reason, with Spanish labels. Counts and reason codes only — never a patient.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "MigrationRun",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929a5e-7c10-7d3e-9a41-5b8f2c6d1e07",
                    "mode": "apply",
                    "status": "completed",
                    "status_label": "Completada",
                    "families": [
                      {
                        "family": "patients",
                        "label": "Fichas administrativas de pacientes (con cargas)",
                        "status": "done",
                        "status_label": "Lista",
                        "reason": null,
                        "reason_label": null,
                        "phase": null,
                        "phase_label": null,
                        "counts": {
                          "examined": 1480,
                          "created": 1402,
                          "unchanged": 60,
                          "gaps": 18
                        },
                        "progress": {
                          "examined": 1480,
                          "expected": 1480
                        },
                        "vendor_requests": 31,
                        "failures": 0,
                        "started_at": "2026-10-30T23:05:02.000Z",
                        "finished_at": "2026-10-30T23:31:40.000Z"
                      }
                    ],
                    "totals": {
                      "examined": 1480,
                      "created": 1402,
                      "unchanged": 60,
                      "gaps": 18
                    },
                    "gaps": [
                      {
                        "family": "patients",
                        "reason": "native_rut_match",
                        "reason_label": "Ya existe en Vitrina una ficha creada aquí con el mismo RUT: no se duplicó ni se fusionó.",
                        "count": 18
                      }
                    ],
                    "history_from": "2021-01-01",
                    "window_ends_at": "2026-11-01T21:00:00.000Z",
                    "started_at": "2026-10-30T23:05:00.000Z",
                    "finished_at": "2026-10-30T23:31:40.000Z",
                    "last_progress_at": "2026-10-30T23:31:40.000Z",
                    "job_deleted_at": "2026-10-30T23:31:41.000Z",
                    "instruction": {
                      "version": "clinic-migration-instruction/2026-09-28",
                      "sha256": "b1946ac92492d2347c6235b4d2611184b1946ac92492d2347c6235b4d2611184",
                      "text": "Instruyo a Vitrina copiar desde Dentalink las fichas administrativas de pacientes …",
                      "instructed_by_user_id": "20000000-0000-4000-8000-000000000001",
                      "instructed_at": "2026-10-30T23:04:40.000Z",
                      "document_reference": null
                    },
                    "error": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/appointments/{id}/services": {
      "get": {
        "summary": "A cita's prestaciones, with what each one owes",
        "description": "The prestación lines of one cita (`clinic_appointment_service`): catalog name, quantity, the unit price and discount FROZEN on the line, `total_clp` (quantity × unit price − discount; 0 when unpriced), whether it was performed, the plan item it performs, and `obligation_status` — the line's own debt: `none` (not performed, or already charged through an accepted presupuesto or a pack), `open` (owed, possibly partly paid), `paid` (fully paid) or `void` (cancelled). `editable` is false with `reason` `vendor_engine` for a Dentalink / Medilink / Reservo cita (its prestaciones live in the vendor's tratamiento), `cancelled` for a cancelled cita, `not_a_clinic_cita` for any other appointment. Never calls the clinic API.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Cita prestaciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "editable": true,
                    "reason": null,
                    "lines": [
                      {
                        "id": "9c1a0001-0000-4000-8000-000000000001",
                        "clinic_service_id": "9c1a0002-0000-4000-8000-000000000001",
                        "nombre": "Limpieza dental",
                        "quantity": 1,
                        "unit_price_clp": 35000,
                        "discount_clp": 0,
                        "total_clp": 35000,
                        "performed": true,
                        "performed_at": "2026-10-21T15:40:00.000Z",
                        "professional_id": "9c1a0003-0000-4000-8000-000000000001",
                        "tooth_or_zone": null,
                        "treatment_plan_item_id": null,
                        "obligation_status": "open"
                      },
                      {
                        "id": "9c1a0001-0000-4000-8000-000000000002",
                        "clinic_service_id": "9c1a0002-0000-4000-8000-000000000002",
                        "nombre": "Resina compuesta",
                        "quantity": 2,
                        "unit_price_clp": 45000,
                        "discount_clp": 10000,
                        "total_clp": 80000,
                        "performed": false,
                        "performed_at": null,
                        "professional_id": "9c1a0003-0000-4000-8000-000000000001",
                        "tooth_or_zone": {
                          "fdi": [
                            16,
                            17
                          ]
                        },
                        "treatment_plan_item_id": "9c1a0004-0000-4000-8000-000000000001",
                        "obligation_status": "none"
                      }
                    ],
                    "total_clp": 115000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a prestación to a native cita",
        "description": "Adds one line. Without `unit_price_clp` the price is the clinic's own resolved price for this patient on the cita's day (price list, convenio, professional price, rules — the same engine as `POST /clinic/pricing/quote`), frozen on the line with its explanation. A cita that belongs to a treatment plan joins the plan: the plan's first pending item of that prestación, or a new item, becomes `scheduled` on this cita and `treatment_plan_item_id` names it. A new line is not performed and owes nothing yet. A Dentalink / Medilink / Reservo cita is never edited here (its prestaciones live in the vendor's own tratamiento) and a cancelled cita is history: both answer 409 with `details.reason` `vendor_engine` / `cancelled`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_service_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99
                  },
                  "unit_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "discount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "tooth_or_zone": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "clinic_service_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "clinic_service_id": "9c1a0002-0000-4000-8000-000000000002",
                "quantity": 2,
                "discount_clp": 10000,
                "tooth_or_zone": {
                  "fdi": [
                    16,
                    17
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Line added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "9c1a0001-0000-4000-8000-000000000002",
                    "clinic_service_id": "9c1a0002-0000-4000-8000-000000000002",
                    "nombre": "Resina compuesta",
                    "quantity": 2,
                    "unit_price_clp": 45000,
                    "discount_clp": 10000,
                    "total_clp": 80000,
                    "performed": false,
                    "performed_at": null,
                    "professional_id": "9c1a0003-0000-4000-8000-000000000001",
                    "tooth_or_zone": {
                      "fdi": [
                        16,
                        17
                      ]
                    },
                    "treatment_plan_item_id": "9c1a0004-0000-4000-8000-000000000001",
                    "obligation_status": "none"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/appointments/{id}/services/{lineId}": {
      "delete": {
        "summary": "Remove a prestación from a native cita",
        "description": "Deletes the line. Its unpaid obligation is cancelled and its plan item goes back to `pending` on the plan. Refused with 409 `line_paid` when pesos are allocated to the line's obligation. A Dentalink / Medilink / Reservo cita is never edited here (its prestaciones live in the vendor's own tratamiento) and a cancelled cita is history: both answer 409 with `details.reason` `vendor_engine` / `cancelled`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "lineId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Line removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "9c1a0001-0000-4000-8000-000000000002",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a native cita's prestación, or mark it performed",
        "description": "Changes quantity, unit price, discount, tooth / zone, or `performed`. PERFORMED IS DEBT: a line marked performed opens one obligation for its total in the patient's ledger (kind `service`, labelled with the prestación's name — which is the line a boleta for a payment against it carries), unless an accepted presupuesto item (through its plan item) or a pack session already charges for it. A price change on a performed line restates that obligation's expected amount. Performing a line linked to a plan item performs the item; un-performing puts it back to `scheduled`. Un-performing cancels the line's unpaid obligation; when pesos are already allocated to it the change is refused with 409 `line_paid` (release the payment in Pagos first). A Dentalink / Medilink / Reservo cita is never edited here (its prestaciones live in the vendor's own tratamiento) and a cancelled cita is history: both answer 409 with `details.reason` `vendor_engine` / `cancelled`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "lineId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 99
                  },
                  "unit_price_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "discount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "tooth_or_zone": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "additionalProperties": {}
                  },
                  "performed": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "performed": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Line updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "9c1a0001-0000-4000-8000-000000000001",
                    "clinic_service_id": "9c1a0002-0000-4000-8000-000000000001",
                    "nombre": "Limpieza dental",
                    "quantity": 1,
                    "unit_price_clp": 35000,
                    "discount_clp": 0,
                    "total_clp": 35000,
                    "performed": true,
                    "performed_at": "2026-10-21T15:40:00.000Z",
                    "professional_id": "9c1a0003-0000-4000-8000-000000000001",
                    "tooth_or_zone": null,
                    "treatment_plan_item_id": null,
                    "obligation_status": "open"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/booking-drafts": {
      "get": {
        "summary": "Abandoned bookings: what patients left on the details step",
        "description": "One row per BOOKING DRAFT: a patient on a public booking surface (the hosted page or the clinic widget) reached the details step and typed a phone. `status` is `open` (not finished yet), `completed` (booked), `recovered` (booked after the WhatsApp recovery message went out — `recovery_fired_at` says when) or `expired` (never finished; the personal fields are scrubbed). `consent_whatsapp` is the unchecked-by-default box on the details step: the recovery Seguimiento never fires without it. `attribution` is the ad click the visit carried, which a recovered booking keeps. The resume token is never listed. Needs `clinic:read` and `contacts:read` (rows carry the phone typed).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "completed",
                "recovered",
                "expired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 100000,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Booking drafts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6f1c2e8a-3b1d-4d5e-9a7b-1c2d3e4f5a6b",
                      "status": "recovered",
                      "surface": "booking_page",
                      "landing_id": "0a9b8c7d-6e5f-4a3b-2c1d-0e9f8a7b6c5d",
                      "service_id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
                      "professional_id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
                      "starts_at": "2026-10-08T14:00:00.000Z",
                      "name": "Ana Pérez",
                      "phone": "+56912345678",
                      "email": null,
                      "consent_whatsapp": true,
                      "attribution": {
                        "utm_source": "facebook",
                        "utm_campaign": "piso-pelvico-octubre",
                        "fbclid": "IwAR2xYz",
                        "surface": "booking_page",
                        "captured_at": "2026-10-07T21:10:00.000Z"
                      },
                      "contact_id": "3d4e5f6a-7b8c-4d9e-0f1a-2b3c4d5e6f7a",
                      "appointment_id": "4e5f6a7b-8c9d-4e0f-1a2b-3c4d5e6f7a8b",
                      "recovery_fired_at": "2026-10-07T22:15:00.000Z",
                      "completed_at": "2026-10-07T22:31:00.000Z",
                      "created_at": "2026-10-07T21:10:00.000Z",
                      "updated_at": "2026-10-07T22:31:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "limit": 50,
                    "offset": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/waitlist/demand": {
      "get": {
        "summary": "Who is waiting for an hour, per specialty",
        "description": "Waitlist demand per specialty — and per professional inside it — over the last `days` (default 30, max 365). The specialty is the professional's (`clinic_professional.especialidad`), because that is what an entry pins; entries for «whoever is free» land in the `specialty: null` group labelled «Cualquier profesional». Every count is DISTINCT PATIENTS: `waiting_now` (a live entry right now — waiting or holding an offer), `joined` (joined inside the window) and `booked` (their entry became a booking inside the window). `totals` is counted on its own, never summed from the groups, so a patient waiting for two specialties is one person. Groups are sorted by `waiting_now`, then `joined`. Aggregates only: no patient is named. This is what the Ads capacity action reads to say «N personas esperan hora con {especialidad}».\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Agenda"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 30
            },
            "required": false,
            "name": "days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Waitlist demand per specialty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicWaitlistDemand"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "days": 30,
                      "from": "2026-08-31T23:00:00.000Z",
                      "to": "2026-09-30T23:00:00.000Z"
                    },
                    "totals": {
                      "waiting_now": 9,
                      "joined": 11,
                      "booked": 2
                    },
                    "by_specialty": [
                      {
                        "specialty": "TTM",
                        "label": "TTM",
                        "waiting_now": 7,
                        "joined": 8,
                        "booked": 1,
                        "professionals": [
                          {
                            "professional_id": "d4d4d4d4-0000-4000-8000-000000000001",
                            "name": "Dra. Profesional Ejemplo",
                            "waiting_now": 7,
                            "joined": 8,
                            "booked": 1
                          }
                        ]
                      },
                      {
                        "specialty": null,
                        "label": "Cualquier profesional",
                        "waiting_now": 2,
                        "joined": 3,
                        "booked": 1,
                        "professionals": [
                          {
                            "professional_id": null,
                            "name": null,
                            "waiting_now": 2,
                            "joined": 3,
                            "booked": 1
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/encounters": {
      "get": {
        "summary": "Atenciones of one patient, newest first, with their notes",
        "description": "The «Atenciones» tab. Each atención carries its evoluciones in order, including the amendment chain: an amended note is a NEW row pointing at the one it corrects via `amends_note_id`, and both stay. Voided atenciones are hidden unless `include_voided=true`. Requires `clinic_record:read` AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_voided",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Atenciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "434a1a10-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                        "professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "treatment_plan_id": null,
                        "form_template_id": null,
                        "kind": "control",
                        "status": "open",
                        "starts_at": "2026-09-22T22:21:17.396Z",
                        "ends_at": null,
                        "closed_at": null,
                        "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "closed_by_user_id": null,
                        "voided_reason": null,
                        "author": {
                          "kind": "member",
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": null,
                          "via": {
                            "kind": "personal_token",
                            "name": "Token personal de la dirección"
                          }
                        },
                        "created_at": "2026-09-22T22:21:17.394Z",
                        "updated_at": "2026-09-22T22:21:17.394Z",
                        "notes": []
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 25
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Open an atención",
        "description": "IDEMPOTENT on `appointment_id`: an attended cita opens exactly one live atención, so the arrival rail, the ficha and a sweep can all call this without minting rivals. `form_template_id` is Reservo's \"tipo de ficha\" (the versioned form this atención is recorded on). `opened_by_user_id` is the MEMBER who opened it and is `null` for an API key; `author` then says which key it was (ADR 0106 §3.1). Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "form_template_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "treatment_plan_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "sterilization_lote": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "consulta",
                      "sesion",
                      "control",
                      "evaluacion",
                      "procedimiento",
                      "teleconsulta"
                    ]
                  },
                  "starts_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                "professional_id": "18342d1b-0000-4000-8000-000000000001",
                "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                "kind": "control"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Atención",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "434a1a10-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "treatment_plan_id": null,
                    "form_template_id": null,
                    "kind": "control",
                    "status": "open",
                    "starts_at": "2026-09-22T22:21:17.396Z",
                    "ends_at": null,
                    "closed_at": null,
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_by_user_id": null,
                    "voided_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.394Z",
                    "updated_at": "2026-09-22T22:21:17.394Z",
                    "notes": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/appointments/{id}/attend": {
      "post": {
        "summary": "«Atender»: open the cita's atención and move it into the box",
        "description": "One call from a cita to its clinical record. Resolves the cita's patient (its own ficha, else the contact's), opens the atención LINKED to the cita — IDEMPOTENT: a second call returns the same atención — with the cita's professional, sede and hour, and moves the sala de espera to `in_room` when the patient is still scheduled or waiting and the caller holds `clinic:write`. Answers `{ encounter, clinic_patient_id, contact_id, arrival_state }`. Requires `clinic_record:write` AND the per-patient PHI gate; refuses a bloqueo, a cancelled or no-show cita, and a cita with no ficha. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Attended cita",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "encounter": {
                      "id": "e0e57121-0000-4000-8000-0000000007ac",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "treatment_plan_id": null,
                      "form_template_id": null,
                      "sterilization_lote": null,
                      "kind": "control",
                      "status": "open",
                      "starts_at": "2026-10-09T18:00:00.000Z",
                      "ends_at": null,
                      "closed_at": null,
                      "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
                      "closed_by_user_id": null,
                      "voided_reason": null,
                      "author": null,
                      "created_at": "2026-10-09T18:00:00.000Z",
                      "updated_at": "2026-10-09T18:00:00.000Z",
                      "notes": []
                    },
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "c0c0c0c0-0000-4000-8000-000000000001",
                    "arrival_state": "in_room"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/encounters/{id}": {
      "get": {
        "summary": "One atención with its evoluciones",
        "description": "Requires `clinic_record:read` AND the PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Atención",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "434a1a10-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "treatment_plan_id": null,
                    "form_template_id": null,
                    "kind": "control",
                    "status": "open",
                    "starts_at": "2026-09-22T22:21:17.396Z",
                    "ends_at": null,
                    "closed_at": null,
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_by_user_id": null,
                    "voided_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.394Z",
                    "updated_at": "2026-09-22T22:21:17.394Z",
                    "notes": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/encounters/{id}/close": {
      "post": {
        "summary": "Close an atención (idempotent)",
        "description": "Closing a closed atención returns it unchanged rather than re-stamping `closed_at` — somebody may already have quoted that time. A voided atención cannot be closed. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Atención",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "434a1a10-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                    "professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                    "treatment_plan_id": null,
                    "form_template_id": null,
                    "kind": "control",
                    "status": "closed",
                    "starts_at": "2026-09-22T22:21:17.396Z",
                    "ends_at": null,
                    "closed_at": "2026-09-22T22:21:17.465Z",
                    "opened_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "closed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "voided_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.394Z",
                    "updated_at": "2026-09-22T22:21:17.464Z",
                    "notes": [
                      {
                        "id": "c51048b7-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "author_user_id": "11111111-0000-4000-8000-000000000001",
                        "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "kind": "evolucion",
                        "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
                        "structured": {},
                        "signed_at": "2026-09-22T22:21:17.444Z",
                        "signature_meta": {
                          "signed_by": "11111111-0000-4000-8000-000000000001",
                          "request_id": "088e29b0-0000-4000-8000-000000000004",
                          "signed_by_kind": "user",
                          "signed_by_label": "direccion+2ab020fd@clinica-ejemplo.cl"
                        },
                        "amends_note_id": null,
                        "amend_reason": null,
                        "author": {
                          "kind": "member",
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": null,
                          "via": {
                            "kind": "personal_token",
                            "name": "Token personal de la dirección"
                          }
                        },
                        "created_at": "2026-09-22T22:21:17.420Z",
                        "amended_note": null
                      },
                      {
                        "id": "cc352edb-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "author_user_id": "11111111-0000-4000-8000-000000000001",
                        "author_professional_id": null,
                        "kind": "evolucion",
                        "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
                        "structured": {},
                        "signed_at": "2026-09-22T22:21:17.454Z",
                        "signature_meta": {
                          "signed_by": "11111111-0000-4000-8000-000000000001",
                          "request_id": "088e29b0-0000-4000-8000-000000000005",
                          "signed_by_kind": "user",
                          "signed_by_label": "direccion+2ab020fd@clinica-ejemplo.cl"
                        },
                        "amends_note_id": "c51048b7-0000-4000-8000-000000000001",
                        "amend_reason": "Se omitió el arco inferior en la evolución firmada",
                        "author": {
                          "kind": "member",
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": null,
                          "via": {
                            "kind": "personal_token",
                            "name": "Token personal de la dirección"
                          }
                        },
                        "created_at": "2026-09-22T22:21:17.453Z",
                        "amended_note": {
                          "id": "c51048b7-0000-4000-8000-000000000001",
                          "created_at": "2026-09-22T22:21:17.420Z"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/encounters/{id}/void": {
      "post": {
        "summary": "Void an atención (idempotent)",
        "description": "Voids an atención opened or closed in error; the row stays, with its reason. Voiding an already voided atención returns it unchanged. Refused on a locked atención. The cita lines that closing performed go back to not performed, so their insumos return to stock. The reason is not copied into the audit row. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Abierta por error"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Atención",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e0e57121-0000-4000-8000-0000000007ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "appointment_id": "d93e8eec-0000-4000-8000-000000000001",
                    "professional_id": null,
                    "location_id": null,
                    "treatment_plan_id": null,
                    "form_template_id": null,
                    "sterilization_lote": null,
                    "kind": "control",
                    "status": "voided",
                    "starts_at": "2026-10-09T18:00:00.000Z",
                    "ends_at": null,
                    "closed_at": "2026-10-09T18:40:00.000Z",
                    "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "closed_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "voided_reason": "Abierta por error",
                    "author": null,
                    "created_at": "2026-10-09T18:00:00.000Z",
                    "updated_at": "2026-10-09T18:45:00.000Z",
                    "notes": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/encounters/{id}/sterilization-lote": {
      "put": {
        "summary": "Record the sterilised-pack lote used on an atención",
        "description": "Sets, changes or clears (`lote: null`) the optional lote of the sterilised instrument pack used on this atención (#4004). Free text: it is matched to an Esterilización cycle by value, not by id, so a lote logged on paper or in Dentalink can still be recorded. Refused on a voided or locked atención. The lote is not copied into the audit row. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lote": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  }
                },
                "required": [
                  "lote"
                ],
                "additionalProperties": false
              },
              "example": {
                "lote": "A-0412"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Atención",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e0e57121-0000-4000-8000-0000000006ab",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "professional_id": null,
                    "location_id": null,
                    "treatment_plan_id": null,
                    "form_template_id": null,
                    "sterilization_lote": "A-0412",
                    "kind": "procedimiento",
                    "status": "open",
                    "starts_at": "2026-10-09T18:00:00.000Z",
                    "ends_at": null,
                    "closed_at": null,
                    "opened_by_user_id": "5e5e5e5e-0000-4000-8000-000000000001",
                    "closed_by_user_id": null,
                    "voided_reason": null,
                    "author": null,
                    "created_at": "2026-10-09T18:00:00.000Z",
                    "updated_at": "2026-10-09T18:05:00.000Z",
                    "notes": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/encounters/{id}/notes": {
      "get": {
        "summary": "Evoluciones of one atención, oldest first",
        "description": "The order the amendment chain is read in. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Evoluciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "c51048b7-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "author_user_id": "11111111-0000-4000-8000-000000000001",
                        "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "kind": "evolucion",
                        "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
                        "structured": {},
                        "signed_at": null,
                        "signature_meta": null,
                        "amends_note_id": null,
                        "amend_reason": null,
                        "author": {
                          "kind": "member",
                          "id": "11111111-0000-4000-8000-000000000001",
                          "name": null,
                          "via": {
                            "kind": "personal_token",
                            "name": "Token personal de la dirección"
                          }
                        },
                        "created_at": "2026-09-22T22:21:17.420Z",
                        "amended_note": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Write an evolución",
        "description": "Authored by a NAMED WRITER — an agent may never write a clinical note (ADR 0083 §8); it records what a patient reports as an antecedente instead. `sign: true` signs it as written, after which the row is IMMUTABLE and the only correction path is `/notes/{id}/amend`. The writer is named honestly: `author_user_id` is the MEMBER who wrote it and is `null` for an API key, and `author` carries what that column cannot — the key with the name it had at that moment, or the member plus the connected app / personal token they acted through (ADR 0106 §3.1). A connected app never reaches this route at all. `performed_plan_item_ids` (with `sign: true` and the `clinic_money:write` scope) marks those pending treatment-plan lines of this patient «realizada» in the same transaction and freezes what was performed in `structured.performed_plan_items`; a line of another patient is a 400 and a line that is no longer pending is a 409. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20000
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "anamnesis",
                      "evolucion",
                      "indicaciones",
                      "interconsulta",
                      "administrativa"
                    ]
                  },
                  "structured": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "author_professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "sign": {
                    "type": "boolean"
                  },
                  "performed_plan_item_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 30
                  }
                },
                "required": [
                  "body"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "evolucion",
                "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
                "author_professional_id": "18342d1b-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evolución",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c51048b7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "kind": "evolucion",
                    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido.",
                    "structured": {},
                    "signed_at": null,
                    "signature_meta": null,
                    "amends_note_id": null,
                    "amend_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.420Z",
                    "amended_note": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/notes/{id}": {
      "patch": {
        "summary": "Correct an UNSIGNED evolución in place",
        "description": "Drafts only. A SIGNED note is immutable and this endpoint 400s pointing at `/notes/{id}/amend` — the repo refuses the update and so does a database trigger. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20000
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "anamnesis",
                      "evolucion",
                      "indicaciones",
                      "interconsulta",
                      "administrativa"
                    ]
                  },
                  "structured": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evolución",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c51048b7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "kind": "evolucion",
                    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
                    "structured": {},
                    "signed_at": null,
                    "signature_meta": null,
                    "amends_note_id": null,
                    "amend_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.420Z",
                    "amended_note": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/notes/{id}/sign": {
      "post": {
        "summary": "Sign an evolución",
        "description": "Locks the row forever. Signing an already-signed note 400s rather than re-stamping the signature: the second signer is a person who believes they just signed something. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Evolución",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c51048b7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_professional_id": "18342d1b-0000-4000-8000-000000000001",
                    "kind": "evolucion",
                    "body": "Control de ortodoncia. Se ajusta arco superior; sin dolor referido. Próximo control en 30 días.",
                    "structured": {},
                    "signed_at": "2026-09-22T22:21:17.444Z",
                    "signature_meta": {
                      "signed_by": "11111111-0000-4000-8000-000000000001",
                      "request_id": "088e29b0-0000-4000-8000-000000000004",
                      "signed_by_kind": "user",
                      "signed_by_label": "direccion+2ab020fd@clinica-ejemplo.cl"
                    },
                    "amends_note_id": null,
                    "amend_reason": null,
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.420Z",
                    "amended_note": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/notes/{id}/amend": {
      "post": {
        "summary": "Amend a signed evolución (a NEW row, never an edit)",
        "description": "The regulatory shape (Ley 20.584 / Ley 21.719): the successor is born signed, carries a MANDATORY reason and points at the note it corrects. One successor per note, so the history is a chain and not a tree of rival versions. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 20000
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 500
                  },
                  "structured": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "body",
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
                "reason": "Se omitió el arco inferior en la evolución firmada"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Evolución",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "cc352edb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_professional_id": null,
                    "kind": "evolucion",
                    "body": "Control de ortodoncia. Se ajusta arco superior e inferior; sin dolor referido.",
                    "structured": {},
                    "signed_at": "2026-09-22T22:21:17.454Z",
                    "signature_meta": {
                      "signed_by": "11111111-0000-4000-8000-000000000001",
                      "request_id": "088e29b0-0000-4000-8000-000000000005",
                      "signed_by_kind": "user",
                      "signed_by_label": "direccion+2ab020fd@clinica-ejemplo.cl"
                    },
                    "amends_note_id": "c51048b7-0000-4000-8000-000000000001",
                    "amend_reason": "Se omitió el arco inferior en la evolución firmada",
                    "author": {
                      "kind": "member",
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": null,
                      "via": {
                        "kind": "personal_token",
                        "name": "Token personal de la dirección"
                      }
                    },
                    "created_at": "2026-09-22T22:21:17.453Z",
                    "amended_note": {
                      "id": "c51048b7-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T22:21:17.420Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients/{id}/antecedentes": {
      "get": {
        "summary": "Antecedentes and patient alerts",
        "description": "The «Ficha clínica» tab: live antecedentes (mórbidos, quirúrgicos, alérgicos, medicamentos, hábitos, familiares) AND live alerts, in one answer — one tab is one disclosure and therefore one access-log row. `include_superseded=true` adds the corrected rows. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_superseded",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Antecedentes y alertas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "antecedentes": [
                      {
                        "id": "02db347c-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "kind": "alergia",
                        "label": "Alergia a penicilina",
                        "detail": "Referida por la paciente; reacción cutánea",
                        "severity": "severa",
                        "status": "active",
                        "onset_on": null,
                        "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "recorded_source": "staff",
                        "superseded_at": null,
                        "superseded_by": null,
                        "superseded_reason": null,
                        "created_at": "2026-09-22T22:21:17.476Z"
                      }
                    ],
                    "flags": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Record an antecedente",
        "description": "Append-only. Correcting one is `/antecedentes/{id}/supersede`, never an edit — what the record said when a decision was taken has to stay readable. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "alergia",
                      "patologia",
                      "medicamento",
                      "cirugia",
                      "habito",
                      "familiar",
                      "embarazo",
                      "otro"
                    ]
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "detail": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "severity": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "leve",
                      "moderada",
                      "severa",
                      "critica"
                    ]
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "resolved"
                    ]
                  },
                  "onset_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "kind",
                  "label"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "alergia",
                "label": "Alergia a penicilina",
                "detail": "Referida por la paciente; reacción cutánea",
                "severity": "severa"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Antecedente",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "02db347c-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "kind": "alergia",
                    "label": "Alergia a penicilina",
                    "detail": "Referida por la paciente; reacción cutánea",
                    "severity": "severa",
                    "status": "active",
                    "onset_on": null,
                    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "recorded_source": "staff",
                    "superseded_at": null,
                    "superseded_by": null,
                    "superseded_reason": null,
                    "created_at": "2026-09-22T22:21:17.476Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/antecedentes/{id}/supersede": {
      "post": {
        "summary": "Supersede an antecedente with a successor",
        "description": "Inserts the successor and stamps `superseded_at` / `superseded_by` on the predecessor — and NOTHING else on it. Omit `successor` to retire an item that was simply wrong; it is still never deleted. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "successor": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "kind": {
                        "type": "string",
                        "enum": [
                          "alergia",
                          "patologia",
                          "medicamento",
                          "cirugia",
                          "habito",
                          "familiar",
                          "embarazo",
                          "otro"
                        ]
                      },
                      "label": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "detail": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 2000
                      },
                      "severity": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "leve",
                          "moderada",
                          "severa",
                          "critica"
                        ]
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "active",
                          "resolved"
                        ]
                      },
                      "onset_on": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      }
                    },
                    "required": [
                      "kind",
                      "label"
                    ],
                    "additionalProperties": false
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason": "Confirmada por el alergólogo, con severidad distinta",
                "successor": {
                  "kind": "alergia",
                  "label": "Alergia a penicilina",
                  "detail": "Confirmada por test cutáneo",
                  "severity": "critica"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Antecedente superseded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "superseded": {
                      "id": "02db347c-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "kind": "alergia",
                      "label": "Alergia a penicilina",
                      "detail": "Referida por la paciente; reacción cutánea",
                      "severity": "severa",
                      "status": "active",
                      "onset_on": null,
                      "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                      "recorded_source": "staff",
                      "superseded_at": "2026-09-22T22:21:17.500Z",
                      "superseded_by": "a455d934-0000-4000-8000-000000000001",
                      "superseded_reason": "Confirmada por el alergólogo, con severidad distinta",
                      "created_at": "2026-09-22T22:21:17.476Z"
                    },
                    "successor": {
                      "id": "a455d934-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "kind": "alergia",
                      "label": "Alergia a penicilina",
                      "detail": "Confirmada por test cutáneo",
                      "severity": "critica",
                      "status": "active",
                      "onset_on": null,
                      "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                      "recorded_source": "staff",
                      "superseded_at": null,
                      "superseded_by": null,
                      "superseded_reason": null,
                      "created_at": "2026-09-22T22:21:17.496Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients/{id}/flags": {
      "post": {
        "summary": "Raise a patient alert",
        "description": "alergia · riesgo · vip · no_agendar_sin_abono · cuidado_especial · custom. THERE IS NO `deudor` KIND: what a patient owes is the payments ledger's answer, it changes with every payment, and a stale red debt banner on a clinical record is both wrong and humiliating. `shown_on_agenda` broadcasts the label to everyone who can read the diary. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "alergia",
                      "riesgo",
                      "vip",
                      "no_agendar_sin_abono",
                      "cuidado_especial",
                      "custom"
                    ]
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "detail": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "severity": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "leve",
                      "moderada",
                      "severa",
                      "critica"
                    ]
                  },
                  "shown_on_agenda": {
                    "type": "boolean"
                  },
                  "history_item_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "kind",
                  "label"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "alergia",
                "label": "Alergia a penicilina",
                "severity": "severa",
                "shown_on_agenda": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Alerta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bb4c2278-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "kind": "alergia",
                    "label": "Alergia a penicilina",
                    "detail": null,
                    "severity": "severa",
                    "shown_on_agenda": true,
                    "history_item_id": null,
                    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "recorded_source": "staff",
                    "resolved_at": null,
                    "resolved_by_user_id": null,
                    "resolved_reason": null,
                    "created_at": "2026-09-22T22:21:17.510Z",
                    "updated_at": "2026-09-22T22:21:17.510Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/flags/{id}/resolve": {
      "post": {
        "summary": "Lift a patient alert",
        "description": "The row stays — who raised it and why is part of the record. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason": "Descartada por el alergólogo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Alerta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bb4c2278-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "kind": "alergia",
                    "label": "Alergia a penicilina",
                    "detail": null,
                    "severity": "severa",
                    "shown_on_agenda": true,
                    "history_item_id": null,
                    "recorded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "recorded_source": "staff",
                    "resolved_at": "2026-09-22T22:21:17.527Z",
                    "resolved_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "resolved_reason": "Descartada por el alergólogo",
                    "created_at": "2026-09-22T22:21:17.510Z",
                    "updated_at": "2026-09-22T22:21:17.526Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/record/agenda-flags": {
      "get": {
        "summary": "Agenda-shown alerts for a page of patients",
        "description": "The agenda block tooltip. Gated on `clinic:read`, NOT `clinic_record:read`: a flag marked `shown_on_agenda` is one the clinic decided everybody who can see the diary should read, and a receptionist who may not open the ficha still has to know not to seat this patient without an abono. Nothing else about the record comes back. At most 200 patient ids per call, and each disclosed patient gets an access-log row.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 8000
            },
            "required": true,
            "name": "patient_ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Alertas por paciente",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "b0e5fda8-6324-4bbd-894c-8722acddf7b8": [
                      {
                        "id": "bb4c2278-0000-4000-8000-000000000001",
                        "kind": "alergia",
                        "label": "Alergia a penicilina",
                        "severity": "severa"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/record-events": {
      "get": {
        "summary": "Who looked at this clinical record",
        "description": "The PHI access log for one patient, newest first: who, what object, which action (view | create | amend | sign | export | print | delete | share), when, and from where (route + user agent). Refused attempts are here too, with `detail.refused`. Requires BOTH `clinic_record:read` and `clinic_admin:write` — auditing colleagues is a compliance authority, not a clinical one. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "view",
                "create",
                "amend",
                "sign",
                "export",
                "print",
                "delete",
                "share"
              ]
            },
            "required": false,
            "name": "action",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Accesos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "496",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "actor_kind": "api_key",
                        "actor_id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "actor_label": null,
                        "action": "view",
                        "entity": "clinic_record_access_log",
                        "entity_id": null,
                        "route": "GET /clinic/patients/:id/record-events",
                        "user_agent": "node",
                        "request_id": "088e29b0-0000-4000-8000-000000000001",
                        "at": "2026-09-22T22:21:17.633Z",
                        "detail": {}
                      },
                      {
                        "id": "495",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "actor_kind": "user",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                        "action": "amend",
                        "entity": "clinic_specialty_chart_finding",
                        "entity_id": "e29e8d94-0000-4000-8000-000000000001",
                        "route": "POST /clinic/chart-findings/:findingId/reopen",
                        "user_agent": "node",
                        "request_id": "088e29b0-0000-4000-8000-000000000002",
                        "at": "2026-09-22T22:21:17.620Z",
                        "detail": {}
                      },
                      {
                        "id": "494",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "actor_kind": "user",
                        "actor_id": "11111111-0000-4000-8000-000000000001",
                        "actor_label": "direccion+2ab020fd@clinica-ejemplo.cl",
                        "action": "amend",
                        "entity": "clinic_specialty_chart_finding",
                        "entity_id": "e29e8d94-0000-4000-8000-000000000001",
                        "route": "POST /clinic/chart-findings/:findingId/resolve",
                        "user_agent": "node",
                        "request_id": "088e29b0-0000-4000-8000-000000000003",
                        "at": "2026-09-22T22:21:17.612Z",
                        "detail": {}
                      }
                    ],
                    "total": 28,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/record-events/export": {
      "get": {
        "summary": "The access log as CSV",
        "description": "The artefact handed to a patient exercising their Ley 21.719 access right, or to a reviewer. Logged as an `export`, which is a different disclosure from a `view`: a file that leaves the building is not a screen somebody looked at.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The access log as CSV",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/form-templates": {
      "get": {
        "summary": "Form templates (the configurable ficha)",
        "description": "The «tipo de ficha» catalogue. Requires `clinic:read` — a receptionist picking a ficha must be able to list them. Templates name no patient, so they write no PHI access event.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "retired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "intake",
                "encounter",
                "followup"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "specialty_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "patient_fillable",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Form templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "3226ecbe-0000-4000-8000-000000000002",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "name": "Ficha de ingreso",
                        "description": null,
                        "specialty_id": null,
                        "kind": "intake",
                        "status": "draft",
                        "patient_fillable": true,
                        "current_version": null,
                        "published_at": null,
                        "retired_at": null,
                        "created_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.699Z",
                        "updated_at": "2026-09-22T22:21:17.699Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a form template (with its first draft version)",
        "description": "A template is never born without a version — the row and its v1 draft are written in one transaction. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  },
                  "specialty_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "intake",
                      "encounter",
                      "followup"
                    ]
                  },
                  "patient_fillable": {
                    "type": "boolean"
                  },
                  "schema": {
                    "type": "object",
                    "properties": {
                      "sections": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "key": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "title": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            },
                            "fields": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 80,
                                    "pattern": "^[a-z0-9_]+$"
                                  },
                                  "type": {
                                    "type": "string",
                                    "enum": [
                                      "text",
                                      "textarea",
                                      "number",
                                      "select",
                                      "multiselect",
                                      "checkbox",
                                      "date",
                                      "signature",
                                      "photo"
                                    ]
                                  },
                                  "label": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 300
                                  },
                                  "required": {
                                    "type": "boolean"
                                  },
                                  "help": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "maxLength": 500
                                  },
                                  "options": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "value": {
                                          "type": "string",
                                          "minLength": 1,
                                          "maxLength": 120
                                        },
                                        "label": {
                                          "type": "string",
                                          "minLength": 1,
                                          "maxLength": 200
                                        }
                                      },
                                      "required": [
                                        "value",
                                        "label"
                                      ]
                                    },
                                    "maxItems": 100
                                  }
                                },
                                "required": [
                                  "key",
                                  "type",
                                  "label"
                                ]
                              },
                              "maxItems": 200
                            }
                          },
                          "required": [
                            "key",
                            "title",
                            "fields"
                          ]
                        },
                        "maxItems": 50
                      }
                    },
                    "required": [
                      "sections"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Ficha de ingreso",
                "kind": "intake",
                "patient_fillable": true,
                "schema": {
                  "sections": [
                    {
                      "key": "salud",
                      "title": "Antecedentes de salud",
                      "fields": [
                        {
                          "key": "alergias",
                          "type": "text",
                          "label": "¿Tiene alergias?",
                          "required": true
                        },
                        {
                          "key": "embarazo",
                          "type": "select",
                          "label": "¿Está embarazada?",
                          "options": [
                            {
                              "value": "si",
                              "label": "Sí"
                            },
                            {
                              "value": "no",
                              "label": "No"
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Form template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ficha de ingreso",
                      "description": null,
                      "specialty_id": null,
                      "kind": "intake",
                      "status": "draft",
                      "patient_fillable": true,
                      "current_version": null,
                      "published_at": null,
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.699Z"
                    },
                    "version": {
                      "id": "c692273d-0000-4000-8000-000000000003",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000002",
                      "version": 1,
                      "schema": {
                        "sections": [
                          {
                            "key": "salud",
                            "title": "Antecedentes de salud",
                            "fields": [
                              {
                                "key": "alergias",
                                "type": "text",
                                "label": "¿Tiene alergias?",
                                "required": true
                              },
                              {
                                "key": "embarazo",
                                "type": "select",
                                "label": "¿Está embarazada?",
                                "options": [
                                  {
                                    "label": "Sí",
                                    "value": "si"
                                  },
                                  {
                                    "label": "No",
                                    "value": "no"
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      },
                      "status": "draft",
                      "change_note": null,
                      "published_at": null,
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.699Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/form-templates/{id}": {
      "get": {
        "summary": "One template with every version, the live one and the draft",
        "description": "The editor reads this: `live` is what a new response would pin, `draft` is what «publicar cambios» would promote. Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Form template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ficha de ingreso",
                      "description": null,
                      "specialty_id": null,
                      "kind": "intake",
                      "status": "draft",
                      "patient_fillable": true,
                      "current_version": null,
                      "published_at": null,
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.699Z"
                    },
                    "versions": [
                      {
                        "id": "c692273d-0000-4000-8000-000000000003",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "template_id": "3226ecbe-0000-4000-8000-000000000002",
                        "version": 1,
                        "schema": {
                          "sections": [
                            {
                              "key": "salud",
                              "title": "Antecedentes de salud",
                              "fields": [
                                {
                                  "key": "alergias",
                                  "type": "text",
                                  "label": "¿Tiene alergias?",
                                  "required": true
                                },
                                {
                                  "key": "embarazo",
                                  "type": "select",
                                  "label": "¿Está embarazada?",
                                  "options": [
                                    {
                                      "label": "Sí",
                                      "value": "si"
                                    },
                                    {
                                      "label": "No",
                                      "value": "no"
                                    }
                                  ]
                                }
                              ]
                            }
                          ]
                        },
                        "status": "draft",
                        "change_note": null,
                        "published_at": null,
                        "published_by_user_id": null,
                        "created_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.699Z",
                        "updated_at": "2026-09-22T22:21:17.699Z"
                      }
                    ],
                    "live": null,
                    "draft": {
                      "id": "c692273d-0000-4000-8000-000000000003",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000002",
                      "version": 1,
                      "schema": {
                        "sections": [
                          {
                            "key": "salud",
                            "title": "Antecedentes de salud",
                            "fields": [
                              {
                                "key": "alergias",
                                "type": "text",
                                "label": "¿Tiene alergias?",
                                "required": true
                              },
                              {
                                "key": "embarazo",
                                "type": "select",
                                "label": "¿Está embarazada?",
                                "options": [
                                  {
                                    "label": "Sí",
                                    "value": "si"
                                  },
                                  {
                                    "label": "No",
                                    "value": "no"
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      },
                      "status": "draft",
                      "change_note": null,
                      "published_at": null,
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.699Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Rename or re-scope a template",
        "description": "IDENTITY ONLY — never the questions. Renaming a ficha must not fork it, which is exactly why the questions live on a version and not here. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  },
                  "specialty_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "intake",
                      "encounter",
                      "followup"
                    ]
                  },
                  "patient_fillable": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "description": "La completa el paciente antes de la primera atención"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Form template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "3226ecbe-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ficha de ingreso",
                    "description": "La completa el paciente antes de la primera atención",
                    "specialty_id": null,
                    "kind": "intake",
                    "status": "draft",
                    "patient_fillable": true,
                    "current_version": null,
                    "published_at": null,
                    "retired_at": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.699Z",
                    "updated_at": "2026-09-22T22:21:17.730Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/form-templates/{id}/draft": {
      "post": {
        "summary": "Save the questions of the draft version",
        "description": "Creates the draft if there is none. A PUBLISHED version can never be edited — the database refuses it — because it is what existing responses render against. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "schema": {
                    "type": "object",
                    "properties": {
                      "sections": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "key": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "title": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 200
                            },
                            "fields": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 80,
                                    "pattern": "^[a-z0-9_]+$"
                                  },
                                  "type": {
                                    "type": "string",
                                    "enum": [
                                      "text",
                                      "textarea",
                                      "number",
                                      "select",
                                      "multiselect",
                                      "checkbox",
                                      "date",
                                      "signature",
                                      "photo"
                                    ]
                                  },
                                  "label": {
                                    "type": "string",
                                    "minLength": 1,
                                    "maxLength": 300
                                  },
                                  "required": {
                                    "type": "boolean"
                                  },
                                  "help": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "maxLength": 500
                                  },
                                  "options": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "value": {
                                          "type": "string",
                                          "minLength": 1,
                                          "maxLength": 120
                                        },
                                        "label": {
                                          "type": "string",
                                          "minLength": 1,
                                          "maxLength": 200
                                        }
                                      },
                                      "required": [
                                        "value",
                                        "label"
                                      ]
                                    },
                                    "maxItems": 100
                                  }
                                },
                                "required": [
                                  "key",
                                  "type",
                                  "label"
                                ]
                              },
                              "maxItems": 200
                            }
                          },
                          "required": [
                            "key",
                            "title",
                            "fields"
                          ]
                        },
                        "maxItems": 50
                      }
                    },
                    "required": [
                      "sections"
                    ]
                  },
                  "change_note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 400
                  }
                },
                "required": [
                  "schema"
                ]
              },
              "example": {
                "change_note": "Se agrega la pregunta por medicamentos",
                "schema": {
                  "sections": [
                    {
                      "key": "salud",
                      "title": "Antecedentes de salud",
                      "fields": [
                        {
                          "key": "alergias",
                          "type": "text",
                          "label": "¿Tiene alergias?",
                          "required": true
                        },
                        {
                          "key": "medicamentos",
                          "type": "text",
                          "label": "¿Toma algún medicamento?"
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Form template version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c692273d-0000-4000-8000-000000000003",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "version": 1,
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    },
                    "status": "draft",
                    "change_note": "Se agrega la pregunta por medicamentos",
                    "published_at": null,
                    "published_by_user_id": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.699Z",
                    "updated_at": "2026-09-22T22:21:17.739Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/form-templates/{id}/publish": {
      "post": {
        "summary": "Publish the draft version",
        "description": "The draft becomes live, the previous live version is retired, and the template points at the new number — one transaction. Existing responses keep rendering the version they pinned. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Form template version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ficha de ingreso",
                      "description": "La completa el paciente antes de la primera atención",
                      "specialty_id": null,
                      "kind": "intake",
                      "status": "published",
                      "patient_fillable": true,
                      "current_version": 1,
                      "published_at": "2026-09-22T22:21:17.748Z",
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.746Z"
                    },
                    "version": {
                      "id": "c692273d-0000-4000-8000-000000000003",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000002",
                      "version": 1,
                      "schema": {
                        "sections": [
                          {
                            "key": "salud",
                            "title": "Antecedentes de salud",
                            "fields": [
                              {
                                "key": "alergias",
                                "type": "text",
                                "label": "¿Tiene alergias?",
                                "required": true
                              },
                              {
                                "key": "medicamentos",
                                "type": "text",
                                "label": "¿Toma algún medicamento?"
                              }
                            ]
                          }
                        ]
                      },
                      "status": "published",
                      "change_note": "Se agrega la pregunta por medicamentos",
                      "published_at": "2026-09-22T22:21:17.748Z",
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.699Z",
                      "updated_at": "2026-09-22T22:21:17.746Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/form-templates/{id}/versions": {
      "post": {
        "summary": "Start version n+1 from the live one",
        "description": "Seeded from the published version, because a new version of a ficha is almost always the old one plus a question. Idempotent: a template that already has a draft returns it. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Form template version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c692273d-0000-4000-8000-000000000004",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "version": 2,
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    },
                    "status": "draft",
                    "change_note": null,
                    "published_at": null,
                    "published_by_user_id": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.756Z",
                    "updated_at": "2026-09-22T22:21:17.756Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/form-templates/{id}/retire": {
      "post": {
        "summary": "Retire a template",
        "description": "No new responses; every existing one still renders forever. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Form template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "f5a82842-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ficha en desuso",
                    "description": null,
                    "specialty_id": null,
                    "kind": "intake",
                    "status": "retired",
                    "patient_fillable": false,
                    "current_version": null,
                    "published_at": null,
                    "retired_at": "2026-09-22T22:21:17.785Z",
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.776Z",
                    "updated_at": "2026-09-22T22:21:17.784Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/forms/pending": {
      "get": {
        "summary": "«Fichas pendientes» — intake forms owed before an upcoming cita",
        "description": "A prestación with `requires_form_template_id` owes a SUBMITTED response before its cita. A response that exists as `pending` or `draft` is still owed, and is exactly the row «reenviar enlace» acts on. A worklist read across patients: it carries a name and what is missing, never an answer, so it gates on `clinic_record:read` and opening a row is the disclosure.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending fichas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [],
                    "total": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/forms/requirements": {
      "get": {
        "summary": "Is this cita confirmable? What is still missing",
        "description": "Returns the LIST of missing requirements (forms and consents) rather than a boolean, because a refusal has to name what is missing: «falta la ficha de ingreso» is actionable, «requisitos incompletos» is not. Empty array = confirmable. Requires `clinic_record:read`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "clinic_patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "service_ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Missing requirements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [],
                    "confirmable": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/forms": {
      "get": {
        "summary": "Fichas filled for one patient, newest first",
        "description": "Each response carries the SCHEMA IT WAS ANSWERED AGAINST, not the live one — that is the version pin. Requires `clinic_record:read` AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "encounter_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Fichas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "d00b3981-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "template_id": "3226ecbe-0000-4000-8000-000000000002",
                        "template_version_id": "c692273d-0000-4000-8000-000000000003",
                        "template_version": 1,
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "appointment_id": null,
                        "values": {},
                        "filled_by": "staff",
                        "status": "pending",
                        "submitted_at": null,
                        "submitted_by_user_id": null,
                        "submitted_by_contact_id": null,
                        "submitted_ip": null,
                        "submitted_user_agent": null,
                        "amends_response_id": null,
                        "amend_reason": null,
                        "voided_reason": null,
                        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "created_at": "2026-09-22T22:21:18.106Z",
                        "updated_at": "2026-09-22T22:21:18.106Z",
                        "template_name": "Ficha de ingreso",
                        "schema": {
                          "sections": [
                            {
                              "key": "salud",
                              "title": "Antecedentes de salud",
                              "fields": [
                                {
                                  "key": "alergias",
                                  "type": "text",
                                  "label": "¿Tiene alergias?",
                                  "required": true
                                },
                                {
                                  "key": "medicamentos",
                                  "type": "text",
                                  "label": "¿Toma algún medicamento?"
                                }
                              ]
                            }
                          ]
                        }
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Open a ficha against the template’s live version",
        "description": "The version is resolved ONCE, here, and stored on the row; nothing downstream consults `current_version` again. `as_request: true` mints it as `pending` — a link to send rather than a form being typed. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "values": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "as_request": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "template_id"
                ]
              },
              "example": {
                "template_id": "3226ecbe-0000-4000-8000-000000000002",
                "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                "as_request": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ficha",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d00b3981-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "template_version_id": "c692273d-0000-4000-8000-000000000003",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "values": {},
                    "filled_by": "staff",
                    "status": "pending",
                    "submitted_at": null,
                    "submitted_by_user_id": null,
                    "submitted_by_contact_id": null,
                    "submitted_ip": null,
                    "submitted_user_agent": null,
                    "amends_response_id": null,
                    "amend_reason": null,
                    "voided_reason": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:18.106Z",
                    "updated_at": "2026-09-22T22:21:18.106Z",
                    "template_name": "Ficha de ingreso",
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/forms/{id}": {
      "get": {
        "summary": "One filled ficha, with the questions it was answered against",
        "description": "Requires `clinic_record:read` AND the PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Ficha",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d00b3981-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "template_version_id": "c692273d-0000-4000-8000-000000000003",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "values": {},
                    "filled_by": "staff",
                    "status": "pending",
                    "submitted_at": null,
                    "submitted_by_user_id": null,
                    "submitted_by_contact_id": null,
                    "submitted_ip": null,
                    "submitted_user_agent": null,
                    "amends_response_id": null,
                    "amend_reason": null,
                    "voided_reason": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:18.106Z",
                    "updated_at": "2026-09-22T22:21:18.106Z",
                    "template_name": "Ficha de ingreso",
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "patch": {
        "summary": "Save answers into an unfinished ficha",
        "description": "Only while `pending` or `draft`. A SUBMITTED ficha is refused here — the correction path is `/forms/{id}/amend`, and the repo, the service and a database trigger each refuse the in-place edit. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "values": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "values"
                ]
              },
              "example": {
                "values": {
                  "alergias": "Penicilina"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ficha",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d00b3981-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "template_version_id": "c692273d-0000-4000-8000-000000000003",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "values": {
                      "alergias": "Penicilina"
                    },
                    "filled_by": "staff",
                    "status": "draft",
                    "submitted_at": null,
                    "submitted_by_user_id": null,
                    "submitted_by_contact_id": null,
                    "submitted_ip": null,
                    "submitted_user_agent": null,
                    "amends_response_id": null,
                    "amend_reason": null,
                    "voided_reason": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:18.106Z",
                    "updated_at": "2026-09-22T22:21:18.165Z",
                    "template_name": "Ficha de ingreso",
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/forms/{id}/submit": {
      "post": {
        "summary": "Submit a ficha (the last write that ever touches its answers)",
        "description": "Required answers are validated against the PINNED version, never the live one: a template that gained a required question yesterday must not make a link sent last week unanswerable. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "values": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "values"
                ]
              },
              "example": {
                "values": {
                  "alergias": "Penicilina",
                  "medicamentos": "Ninguno"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ficha",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d00b3981-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "template_version_id": "c692273d-0000-4000-8000-000000000003",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "values": {
                      "alergias": "Penicilina",
                      "medicamentos": "Ninguno"
                    },
                    "filled_by": "staff",
                    "status": "submitted",
                    "submitted_at": "2026-09-22T22:21:18.177Z",
                    "submitted_by_user_id": null,
                    "submitted_by_contact_id": null,
                    "submitted_ip": null,
                    "submitted_user_agent": null,
                    "amends_response_id": null,
                    "amend_reason": null,
                    "voided_reason": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:18.106Z",
                    "updated_at": "2026-09-22T22:21:18.176Z",
                    "template_name": "Ficha de ingreso",
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/forms/{id}/amend": {
      "post": {
        "summary": "Amend a submitted ficha (a NEW response, with a reason)",
        "description": "Pinned to the SAME version, pointing at the predecessor, which becomes `amended` and keeps every answer it had. One successor per response: the correction history is a chain, not a tree. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "values": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 600
                  }
                },
                "required": [
                  "values",
                  "reason"
                ]
              },
              "example": {
                "values": {
                  "alergias": "Penicilina y AINEs",
                  "medicamentos": "Ninguno"
                },
                "reason": "La paciente agrega una alergia al revisar la ficha"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Ficha",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "cc352edb-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000002",
                    "template_version_id": "c692273d-0000-4000-8000-000000000003",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "values": {
                      "alergias": "Penicilina y AINEs",
                      "medicamentos": "Ninguno"
                    },
                    "filled_by": "staff",
                    "status": "submitted",
                    "submitted_at": "2026-09-22T22:21:18.190Z",
                    "submitted_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "submitted_by_contact_id": null,
                    "submitted_ip": null,
                    "submitted_user_agent": null,
                    "amends_response_id": "d00b3981-0000-4000-8000-000000000001",
                    "amend_reason": "La paciente agrega una alergia al revisar la ficha",
                    "voided_reason": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:18.188Z",
                    "updated_at": "2026-09-22T22:21:18.189Z",
                    "template_name": "Ficha de ingreso",
                    "schema": {
                      "sections": [
                        {
                          "key": "salud",
                          "title": "Antecedentes de salud",
                          "fields": [
                            {
                              "key": "alergias",
                              "type": "text",
                              "label": "¿Tiene alergias?",
                              "required": true
                            },
                            {
                              "key": "medicamentos",
                              "type": "text",
                              "label": "¿Toma algún medicamento?"
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/forms/{id}/link": {
      "post": {
        "summary": "Mint the tokenised link the patient fills on their phone",
        "description": "Refused for a template that is not `patient_fillable`: a ficha written for a professional to complete in front of a patient is not a ficha to hand the patient. The link is an HS256 capability token; the ROW, not the token, decides whether it is still fillable. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Patient link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "response_id": "d00b3981-0000-4000-8000-000000000001",
                    "url": "http://localhost:3000/ficha/<token>",
                    "expires_in_seconds": 1209600
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/consent-templates": {
      "get": {
        "summary": "Consent templates",
        "description": "Requires `clinic:read`. Templates name no patient, so they write no PHI access event.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "retired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Consent templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "3226ecbe-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "name": "Consentimiento de ortodoncia",
                        "description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
                        "specialty_id": null,
                        "status": "draft",
                        "signature_methods": [
                          "tablet",
                          "public_link"
                        ],
                        "current_version": null,
                        "published_at": null,
                        "retired_at": null,
                        "created_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.641Z",
                        "updated_at": "2026-09-22T22:21:17.641Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a consent template (with its first draft version)",
        "description": "`signature_methods` is per template: a consent a clinic insists on taking in the box must not be signable from a link. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  },
                  "specialty_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "signature_methods": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "tablet",
                        "public_link",
                        "whatsapp_otp",
                        "pdf_upload",
                        "paper_scan"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 5
                  },
                  "body_html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "allows_photos": {
                    "type": "boolean"
                  },
                  "allows_marketing_use": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Consentimiento de ortodoncia",
                "description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
                "signature_methods": [
                  "tablet",
                  "public_link"
                ],
                "body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
                "allows_photos": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Consent template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Consentimiento de ortodoncia",
                      "description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
                      "specialty_id": null,
                      "status": "draft",
                      "signature_methods": [
                        "tablet",
                        "public_link"
                      ],
                      "current_version": null,
                      "published_at": null,
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.641Z"
                    },
                    "version": {
                      "id": "c692273d-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000001",
                      "version": 1,
                      "body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
                      "allows_photos": true,
                      "allows_marketing_use": false,
                      "status": "draft",
                      "change_note": null,
                      "published_at": null,
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.641Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/consent-templates/{id}": {
      "get": {
        "summary": "One consent template with its versions, live and draft",
        "description": "`live` is what a new consent would pin; `allows_photos` / `allows_marketing_use` say what the TEXT covers (the patient’s own answers live on the signed consent). Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Consent template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Consentimiento de ortodoncia",
                      "description": "Tratamiento de ortodoncia fija, riesgos y alternativas",
                      "specialty_id": null,
                      "status": "draft",
                      "signature_methods": [
                        "tablet",
                        "public_link"
                      ],
                      "current_version": null,
                      "published_at": null,
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.641Z"
                    },
                    "versions": [
                      {
                        "id": "c692273d-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "template_id": "3226ecbe-0000-4000-8000-000000000001",
                        "version": 1,
                        "body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
                        "allows_photos": true,
                        "allows_marketing_use": false,
                        "status": "draft",
                        "change_note": null,
                        "published_at": null,
                        "published_by_user_id": null,
                        "created_by_user_id": null,
                        "created_at": "2026-09-22T22:21:17.641Z",
                        "updated_at": "2026-09-22T22:21:17.641Z"
                      }
                    ],
                    "live": null,
                    "draft": {
                      "id": "c692273d-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000001",
                      "version": 1,
                      "body_html": "<p>Declaro haber sido informada del tratamiento propuesto…</p>",
                      "allows_photos": true,
                      "allows_marketing_use": false,
                      "status": "draft",
                      "change_note": null,
                      "published_at": null,
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.641Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Rename, re-scope or change the allowed signature methods",
        "description": "Identity only — never the text. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  },
                  "specialty_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "signature_methods": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "tablet",
                        "public_link",
                        "whatsapp_otp",
                        "pdf_upload",
                        "paper_scan"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 5
                  }
                }
              },
              "example": {
                "description": "Ortodoncia fija: riesgos, alternativas y cuidados"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "3226ecbe-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Consentimiento de ortodoncia",
                    "description": "Ortodoncia fija: riesgos, alternativas y cuidados",
                    "specialty_id": null,
                    "status": "draft",
                    "signature_methods": [
                      "tablet",
                      "public_link"
                    ],
                    "current_version": null,
                    "published_at": null,
                    "retired_at": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.641Z",
                    "updated_at": "2026-09-22T22:21:17.660Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/consent-templates/{id}/draft": {
      "post": {
        "summary": "Save the draft text and its clauses",
        "description": "A PUBLISHED consent text can never be edited — it is the evidence of what a patient agreed to, and editing it retroactively changes what they agreed to. `allows_marketing_use` cannot be set without `allows_photos`. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body_html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "allows_photos": {
                    "type": "boolean"
                  },
                  "allows_marketing_use": {
                    "type": "boolean"
                  },
                  "change_note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 400
                  }
                }
              },
              "example": {
                "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                "allows_photos": true,
                "change_note": "Se agregan las alternativas"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c692273d-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "version": 1,
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false,
                    "status": "draft",
                    "change_note": "Se agregan las alternativas",
                    "published_at": null,
                    "published_by_user_id": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.641Z",
                    "updated_at": "2026-09-22T22:21:17.668Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/consent-templates/{id}/publish": {
      "post": {
        "summary": "Publish the draft version",
        "description": "Refused when the text is empty: the text is what the patient agrees to. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Consent version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "3226ecbe-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Consentimiento de ortodoncia",
                      "description": "Ortodoncia fija: riesgos, alternativas y cuidados",
                      "specialty_id": null,
                      "status": "published",
                      "signature_methods": [
                        "tablet",
                        "public_link"
                      ],
                      "current_version": 1,
                      "published_at": "2026-09-22T22:21:17.677Z",
                      "retired_at": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.675Z"
                    },
                    "version": {
                      "id": "c692273d-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "template_id": "3226ecbe-0000-4000-8000-000000000001",
                      "version": 1,
                      "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                      "allows_photos": true,
                      "allows_marketing_use": false,
                      "status": "published",
                      "change_note": "Se agregan las alternativas",
                      "published_at": "2026-09-22T22:21:17.677Z",
                      "published_by_user_id": null,
                      "created_by_user_id": null,
                      "created_at": "2026-09-22T22:21:17.641Z",
                      "updated_at": "2026-09-22T22:21:17.675Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/consent-templates/{id}/versions": {
      "post": {
        "summary": "Start version n+1 from the live one",
        "description": "Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Consent version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "c692273d-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "version": 2,
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false,
                    "status": "draft",
                    "change_note": null,
                    "published_at": null,
                    "published_by_user_id": null,
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.687Z",
                    "updated_at": "2026-09-22T22:21:17.687Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/consent-templates/{id}/retire": {
      "post": {
        "summary": "Retire a consent template",
        "description": "No new consents; every signed one keeps rendering its pinned text. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Consent template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "f5a82842-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Consentimiento en desuso",
                    "description": null,
                    "specialty_id": null,
                    "status": "retired",
                    "signature_methods": [
                      "tablet",
                      "public_link"
                    ],
                    "current_version": null,
                    "published_at": null,
                    "retired_at": "2026-09-22T22:21:17.772Z",
                    "created_by_user_id": null,
                    "created_at": "2026-09-22T22:21:17.764Z",
                    "updated_at": "2026-09-22T22:21:17.771Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/consents": {
      "get": {
        "summary": "«Consentimientos» — firmados, pendientes y revocados",
        "description": "Across patients, filterable by template and status. A WORKLIST: each row carries a name and a status, never the consent TEXT or a signature. Requires `clinic_record:read`; opening one row is the disclosure that writes an access event.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "clinic_patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "template_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "signed",
                "revoked",
                "expired",
                "voided"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Consents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "15b61788-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "template_id": "3226ecbe-0000-4000-8000-000000000001",
                        "template_version_id": "c692273d-0000-4000-8000-000000000001",
                        "template_version": 1,
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "appointment_id": null,
                        "status": "pending",
                        "signed_by_contact_id": null,
                        "signed_by_name": null,
                        "signed_by_document": null,
                        "signed_by_role": null,
                        "signature_method": null,
                        "signature_storage_key": null,
                        "signature_mime_type": null,
                        "signed_at": null,
                        "signed_ip": null,
                        "signed_user_agent": null,
                        "signed_by_user_id": null,
                        "photo_consent": null,
                        "marketing_use_consent": null,
                        "rendered_document_id": null,
                        "revoked_at": null,
                        "revoked_reason": null,
                        "revoked_by_user_id": null,
                        "expires_at": null,
                        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "created_at": "2026-09-22T22:21:17.807Z",
                        "updated_at": "2026-09-22T22:21:17.807Z",
                        "template_name": "Consentimiento de ortodoncia",
                        "patient_name": "María José Fuentes Lagos"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/consents": {
      "get": {
        "summary": "Consents of one patient, newest first",
        "description": "Each one carries the TEXT it pinned and the patient’s tri-state answers. Requires `clinic_record:read` AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Consents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "15b61788-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "template_id": "3226ecbe-0000-4000-8000-000000000001",
                        "template_version_id": "c692273d-0000-4000-8000-000000000001",
                        "template_version": 1,
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                        "appointment_id": null,
                        "status": "pending",
                        "signed_by_contact_id": null,
                        "signed_by_name": null,
                        "signed_by_document": null,
                        "signed_by_role": null,
                        "signature_method": null,
                        "signature_storage_key": null,
                        "signature_mime_type": null,
                        "signed_at": null,
                        "signed_ip": null,
                        "signed_user_agent": null,
                        "signed_by_user_id": null,
                        "photo_consent": null,
                        "marketing_use_consent": null,
                        "rendered_document_id": null,
                        "revoked_at": null,
                        "revoked_reason": null,
                        "revoked_by_user_id": null,
                        "expires_at": null,
                        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "created_at": "2026-09-22T22:21:17.807Z",
                        "updated_at": "2026-09-22T22:21:17.807Z",
                        "template_name": "Consentimiento de ortodoncia",
                        "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                        "allows_photos": true,
                        "allows_marketing_use": false
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Request a consent (pending, pinned to the live version)",
        "description": "Mints the row a tablet or a link then signs. The version is resolved once, here. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "template_id"
                ]
              },
              "example": {
                "template_id": "3226ecbe-0000-4000-8000-000000000001",
                "encounter_id": "434a1a10-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Consent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15b61788-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "template_version_id": "c692273d-0000-4000-8000-000000000001",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "status": "pending",
                    "signed_by_contact_id": null,
                    "signed_by_name": null,
                    "signed_by_document": null,
                    "signed_by_role": null,
                    "signature_method": null,
                    "signature_storage_key": null,
                    "signature_mime_type": null,
                    "signed_at": null,
                    "signed_ip": null,
                    "signed_user_agent": null,
                    "signed_by_user_id": null,
                    "photo_consent": null,
                    "marketing_use_consent": null,
                    "rendered_document_id": null,
                    "revoked_at": null,
                    "revoked_reason": null,
                    "revoked_by_user_id": null,
                    "expires_at": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:17.807Z",
                    "updated_at": "2026-09-22T22:21:17.807Z",
                    "template_name": "Consentimiento de ortodoncia",
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/consents/imported": {
      "get": {
        "summary": "Consents a migration carried across as documents, newest first",
        "description": "The patient’s documents of kind `consentimiento` that an instructed migration imported (`imported_from` set) — id, title, the vendor’s date and the vendor, never the content (open it through `GET /clinic/documents/{id}/content`). They carry NO signature evidence: they never satisfy a service’s consent requirement nor the gallery gate, which read signed consents only. Requires `clinic_record:read` AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Imported consent documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "d0c5e11a-0000-4000-8000-000000000001",
                        "title": "Consentimiento informado exodoncia",
                        "imported_from": "healthatom",
                        "created_at": "2022-12-29T17:51:14.000Z",
                        "purged_at": null
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/consents/{id}": {
      "get": {
        "summary": "One consent with the text it was signed against",
        "description": "Requires `clinic_record:read` AND the PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Consent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15b61788-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "template_version_id": "c692273d-0000-4000-8000-000000000001",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "status": "pending",
                    "signed_by_contact_id": null,
                    "signed_by_name": null,
                    "signed_by_document": null,
                    "signed_by_role": null,
                    "signature_method": null,
                    "signature_storage_key": null,
                    "signature_mime_type": null,
                    "signed_at": null,
                    "signed_ip": null,
                    "signed_user_agent": null,
                    "signed_by_user_id": null,
                    "photo_consent": null,
                    "marketing_use_consent": null,
                    "rendered_document_id": null,
                    "revoked_at": null,
                    "revoked_reason": null,
                    "revoked_by_user_id": null,
                    "expires_at": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:17.807Z",
                    "updated_at": "2026-09-22T22:21:17.807Z",
                    "template_name": "Consentimiento de ortodoncia",
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/consents/{id}/sign": {
      "post": {
        "summary": "Sign a consent (staff-assisted)",
        "description": "Records the signer’s name, document and role, the method, the signature image, the instant, the IP and the user agent. MINORS: a patient under 18 cannot sign for themself — the signer must be an `apoderado` or a `tutor`. `photo_consent` is TRI-STATE and must not be defaulted: null means the question was never asked. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signed_by_name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 200
                  },
                  "signed_by_document": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "signed_by_role": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "titular",
                      "apoderado",
                      "tutor",
                      "pareja",
                      "otro"
                    ]
                  },
                  "signed_by_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "signature_method": {
                    "type": "string",
                    "enum": [
                      "tablet",
                      "public_link",
                      "whatsapp_otp",
                      "pdf_upload",
                      "paper_scan"
                    ]
                  },
                  "signature_image": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 16,
                    "maxLength": 1400000
                  },
                  "photo_consent": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "marketing_use_consent": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                },
                "required": [
                  "signed_by_name",
                  "signature_method"
                ]
              },
              "example": {
                "signed_by_name": "María José Fuentes Lagos",
                "signed_by_document": "11.111.111-1",
                "signed_by_role": "titular",
                "signed_by_contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "signature_method": "tablet",
                "photo_consent": true,
                "marketing_use_consent": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15b61788-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "template_version_id": "c692273d-0000-4000-8000-000000000001",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": "434a1a10-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "status": "signed",
                    "signed_by_contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "signed_by_name": "María José Fuentes Lagos",
                    "signed_by_document": "11.111.111-1",
                    "signed_by_role": "titular",
                    "signature_method": "tablet",
                    "signature_storage_key": null,
                    "signature_mime_type": null,
                    "signed_at": "2026-09-22T22:21:17.852Z",
                    "signed_ip": "::ffff:127.0.0.1",
                    "signed_user_agent": "node",
                    "signed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "photo_consent": true,
                    "marketing_use_consent": false,
                    "rendered_document_id": null,
                    "revoked_at": null,
                    "revoked_reason": null,
                    "revoked_by_user_id": null,
                    "expires_at": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:17.807Z",
                    "updated_at": "2026-09-22T22:21:17.851Z",
                    "template_name": "Consentimiento de ortodoncia",
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/consents/{id}/revoke": {
      "post": {
        "summary": "Revoke a signed consent (with a reason)",
        "description": "Never a delete: a status, a date and a reason. Revoking a consent with the photo clause immediately closes the gallery gate for that patient. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 600
                  }
                },
                "required": [
                  "reason"
                ]
              },
              "example": {
                "reason": "La paciente retira su autorización para fotografías"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e85a0f49-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "template_id": "3226ecbe-0000-4000-8000-000000000001",
                    "template_version_id": "c692273d-0000-4000-8000-000000000001",
                    "template_version": 1,
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "encounter_id": null,
                    "appointment_id": null,
                    "status": "revoked",
                    "signed_by_contact_id": null,
                    "signed_by_name": "María José Fuentes Lagos",
                    "signed_by_document": null,
                    "signed_by_role": null,
                    "signature_method": "public_link",
                    "signature_storage_key": null,
                    "signature_mime_type": null,
                    "signed_at": "2026-09-22T22:21:17.876Z",
                    "signed_ip": "::ffff:127.0.0.1",
                    "signed_user_agent": "node",
                    "signed_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "photo_consent": true,
                    "marketing_use_consent": null,
                    "rendered_document_id": null,
                    "revoked_at": "2026-09-22T22:21:17.886Z",
                    "revoked_reason": "La paciente retira su autorización para fotografías",
                    "revoked_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "expires_at": null,
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-22T22:21:17.864Z",
                    "updated_at": "2026-09-22T22:21:17.885Z",
                    "template_name": "Consentimiento de ortodoncia",
                    "body_html": "<p>Declaro haber sido informada del tratamiento, sus riesgos y sus alternativas.</p>",
                    "allows_photos": true,
                    "allows_marketing_use": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/consents/{id}/link": {
      "post": {
        "summary": "Mint the tokenised signing link",
        "description": "Refused unless the template allows `public_link`. The token is HS256 and unrevocable, so the ROW’s own status — re-read on every request — is what decides whether it is still signable. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Patient link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "consent_id": "15b61788-0000-4000-8000-000000000001",
                    "url": "http://localhost:3000/consentimiento/<token>",
                    "expires_in_seconds": 1209600
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/photos": {
      "get": {
        "summary": "Galería — before/after images by zona and fecha",
        "description": "Rows sharing a `pair_id` are one side-by-side comparison. Bytes are returned INLINE (`with_bytes=true`) rather than as a signed URL: the bucket is private and a clinical photograph is the last object that should acquire a forwardable link. `photo_consent` in the envelope says whether new images may currently be added. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "zone",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "with_bytes",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinical images",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [],
                    "total": 0,
                    "photo_consent": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Upload a clinical image (CONSENT-GATED)",
        "description": "REFUSED unless a live signed consent exists whose pinned text carries the photo clause AND whose `photo_consent` answer is TRUE — \"never asked\" is not a yes. The authorising consent id is written onto the row (`consent_id` is NOT NULL), so the permission is recorded and not merely checked. The gate runs BEFORE a single byte reaches the bucket. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "zone": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "phase": {
                    "type": "string",
                    "enum": [
                      "antes",
                      "durante",
                      "despues",
                      "control",
                      "otro"
                    ]
                  },
                  "captured_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "pair_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "caption": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 400
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "image": {
                    "type": "string",
                    "minLength": 64,
                    "maxLength": 17000000
                  }
                },
                "required": [
                  "zone",
                  "image"
                ]
              },
              "example": {
                "zone": "frontal",
                "phase": "antes",
                "captured_on": "2026-09-24",
                "caption": "Frontal en máxima intercuspidación",
                "encounter_id": "14141414-0000-4000-8000-000000000001",
                "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinical image",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15151515-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "encounter_id": "14141414-0000-4000-8000-000000000001",
                    "consent_id": null,
                    "zone": "frontal",
                    "phase": "antes",
                    "captured_on": "2026-09-24",
                    "pair_id": null,
                    "mime_type": "image/png",
                    "size_bytes": 95,
                    "caption": "Frontal en máxima intercuspidación",
                    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "deleted_at": null,
                    "deleted_reason": null,
                    "created_at": "2026-09-24T13:41:02.118Z",
                    "updated_at": "2026-09-24T13:41:02.118Z",
                    "data_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/photos/{id}/delete": {
      "post": {
        "summary": "Delete a clinical image (tombstone, with a reason)",
        "description": "The bytes go; the row stays with who uploaded it, under which consent and when. A POST rather than a DELETE because the reason is required. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Consents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 400
                  }
                },
                "required": [
                  "reason"
                ]
              },
              "example": {
                "reason": "Foto desenfocada, se repite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinical image",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "15151515-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "encounter_id": "14141414-0000-4000-8000-000000000001",
                    "consent_id": null,
                    "zone": "frontal",
                    "phase": "antes",
                    "captured_on": "2026-09-24",
                    "pair_id": null,
                    "mime_type": "image/png",
                    "size_bytes": 95,
                    "caption": "Frontal en máxima intercuspidación",
                    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "deleted_at": "2026-09-24T18:02:55.401Z",
                    "deleted_reason": "Foto desenfocada, se repite",
                    "created_at": "2026-09-24T13:41:02.118Z",
                    "updated_at": "2026-09-24T13:41:02.118Z",
                    "data_url": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/retention": {
      "get": {
        "summary": "Retention policy per document kind",
        "description": "EVERY kind is returned, always — a settings screen with holes in it is a screen a clinic cannot reason about. Kinds the tenant never configured render their legal floor and say so (`is_default`). Requires `clinic:read`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "responses": {
          "200": {
            "description": "Retention policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "kind": "examen",
                        "retention_years": 15,
                        "legal_minimum_years": 15,
                        "is_default": true
                      },
                      {
                        "kind": "imagen",
                        "retention_years": 15,
                        "legal_minimum_years": 15,
                        "is_default": true
                      },
                      {
                        "kind": "informe",
                        "retention_years": 15,
                        "legal_minimum_years": 15,
                        "is_default": true
                      }
                    ],
                    "total": 10
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set how many years one kind of document is kept",
        "description": "REFUSED below the legal minimum, with the legal reason — 15 years for clinical documents (Ley 20.584 / Decreto 41), 6 for tax documents (SII). The floor is also a CHECK constraint, so it is a property of the database rather than of a form validator. Requires `clinic_admin:write`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "examen",
                      "imagen",
                      "informe",
                      "receta",
                      "indicaciones",
                      "consentimiento",
                      "presupuesto",
                      "boleta",
                      "certificado",
                      "otro"
                    ]
                  },
                  "retention_years": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "required": [
                  "kind",
                  "retention_years"
                ]
              },
              "example": {
                "kind": "consentimiento",
                "retention_years": 15
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Retention policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "kind": "consentimiento",
                    "retention_years": 15,
                    "legal_minimum_years": 15,
                    "is_default": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/documents": {
      "get": {
        "summary": "PHI documents on one patient’s ficha",
        "description": "`sensitivity` defaults to `phi` because a file uploaded to a clinical record is health data until somebody says otherwise; the ficha marks those rows. Purged rows stay visible as tombstones — \"we held an exam and disposed of it in 2042\" is information. Requires `clinic_record:read` AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "examen",
                "imagen",
                "informe",
                "receta",
                "indicaciones",
                "consentimiento",
                "presupuesto",
                "boleta",
                "certificado",
                "otro"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinical documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [],
                    "total": 0,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Upload a PHI document",
        "description": "THE TTL SHIPS WITH THE COLLECTION (ADR 0083 §5): `retention_until` is stamped from the tenant’s policy for this kind at upload time, and there is no code path that stores a document without one. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "examen",
                      "imagen",
                      "informe",
                      "receta",
                      "indicaciones",
                      "consentimiento",
                      "presupuesto",
                      "boleta",
                      "certificado",
                      "otro"
                    ]
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  },
                  "content": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 28000000
                  },
                  "mime_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "sensitivity": {
                    "type": "string",
                    "enum": [
                      "phi",
                      "administrative"
                    ]
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "kind",
                  "content"
                ]
              },
              "example": {
                "kind": "informe",
                "title": "Informe de evaluación inicial",
                "sensitivity": "phi",
                "encounter_id": "14141414-0000-4000-8000-000000000001",
                "mime_type": "image/png",
                "content": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinical document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "16161616-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "encounter_id": "14141414-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "kind": "informe",
                    "title": "Informe de evaluación inicial",
                    "mime_type": "image/png",
                    "size_bytes": 95,
                    "sensitivity": "phi",
                    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "uploaded_source": "staff",
                    "retention_until": "2041-09-24",
                    "purged_at": null,
                    "purge_reason": null,
                    "created_at": "2026-09-24T13:44:10.552Z",
                    "updated_at": "2026-09-24T13:44:10.552Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/documents/{id}/content": {
      "get": {
        "summary": "Download one document’s bytes",
        "description": "Streams from the private bucket through this route rather than a signed URL. A purged document answers with the date it was disposed of and its reason — the record of it remains, the file does not. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "File bytes",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/documents/{id}/delete": {
      "post": {
        "summary": "Delete a document before its retention runs out (tombstone)",
        "description": "A file uploaded to the wrong ficha must be correctable, so this is allowed — but it writes the SAME tombstone the retention sweep writes, with the operator’s reason instead of «retention», so the two are equally visible in an audit. A POST because the reason is required. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 400
                  }
                },
                "required": [
                  "reason"
                ]
              },
              "example": {
                "reason": "Cargado en la ficha equivocada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinical document",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "16161616-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "encounter_id": "14141414-0000-4000-8000-000000000001",
                    "appointment_id": null,
                    "kind": "informe",
                    "title": "Informe de evaluación inicial",
                    "mime_type": "image/png",
                    "size_bytes": 95,
                    "sensitivity": "phi",
                    "uploaded_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "uploaded_source": "staff",
                    "retention_until": "2041-09-24",
                    "purged_at": "2026-09-24T18:10:33.907Z",
                    "purge_reason": "Cargado en la ficha equivocada",
                    "created_at": "2026-09-24T13:44:10.552Z",
                    "updated_at": "2026-09-24T13:44:10.552Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/privacy-requests": {
      "get": {
        "summary": "Export and erasure requests",
        "description": "The durable record of who asked for what, when, and what came back — including a refused erasure and the date its window closes. Requires `clinic_record:read`.\n\nAn erasure’s `result.engine` is the attribution-data half: `status` `owed` → `queued` → `confirming` → `done` (or `skipped` / `failed`; `done` only once the attribution engine confirms it); `ficha` `erased` | `retained` (retained by law, marketing data erased); `marketing_blocked`; `scrubbed {outbox_rows, threads}` (Vitrina’s own copies of the marketing identifiers removed); `skipped` `no_self_contact` (the ficha has no contact of its own — a guardian is never erased for the patient) or `no_engine_profile`; `receipts[]` `{erasure_id, engine_status, completed_at}`. Say «datos de marketing eliminados» only at `done`. `engine_skipped_no_self_contact` counts the requests that skipped for lack of a contact of their own.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "clinic_patient_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "export",
                "erasure"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Privacy requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "1fa19e08-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "kind": "export",
                        "status": "failed",
                        "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                        "requested_reason": null,
                        "started_at": "2026-09-22T22:21:18.312Z",
                        "completed_at": "2026-09-22T22:21:18.499Z",
                        "storage_key": null,
                        "size_bytes": null,
                        "token_expires_at": null,
                        "result": {},
                        "retention_until": null,
                        "refusal_reason": null,
                        "error": "getaddrinfo ENOTFOUND minio",
                        "created_at": "2026-09-22T22:21:18.308Z",
                        "updated_at": "2026-09-22T22:21:18.498Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/export": {
      "post": {
        "summary": "Export one patient’s whole clinical record",
        "description": "Assembles the ficha, its fichas filled, consents, clinical images and documents — with the documents’ own bytes inline — into one JSON archive, stores it, and returns a download path with a 7-day token. JSON and not a bundle of PDFs because Ley 21.719’s portability right is about the DATA; a rendered PDF is a picture of the data. Recorded as `export` in the PHI access log. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  }
                }
              },
              "example": {
                "reason": "La paciente ejerce su derecho de acceso (Ley 21.719)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Privacy request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "17171717-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "kind": "export",
                    "status": "completed",
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "requested_reason": "La paciente ejerce su derecho de acceso (Ley 21.719)",
                    "started_at": "2026-09-24T18:20:04.001Z",
                    "completed_at": "2026-09-24T18:20:07.430Z",
                    "size_bytes": 148223,
                    "token_expires_at": "2026-10-01T18:20:07.430Z",
                    "result": {
                      "documents": 3,
                      "photos": 1,
                      "notes": 6,
                      "consents": 2
                    },
                    "retention_until": "2026-10-01",
                    "refusal_reason": null,
                    "error": null,
                    "created_at": "2026-09-24T18:20:04.001Z",
                    "updated_at": "2026-09-24T18:20:07.430Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/privacy-requests/{id}/download": {
      "get": {
        "summary": "Download an export archive",
        "description": "The token AND the staff gate are both checked: the token stops a stale link working forever, the gate stops the wrong staff member opening it. The archive is deleted when its token expires — PHI in a bucket must not outlive its link. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 10,
              "maxLength": 4096
            },
            "required": true,
            "name": "token",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Archive bytes",
            "content": {
              "application/zip": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/erase": {
      "post": {
        "summary": "Erasure request (Ley 21.719), bounded by the retention floor",
        "description": "INSIDE the 15-year window (Ley 20.584, Decreto 41) the request is REFUSED — with the legal reason and the date it may be executed — and the refusal is recorded; a silent refusal is as bad as a wrongful deletion. OUTSIDE it, the PII is tombstoned and the clinical rows stay for their own retention. A refusal answers 201, not a 4xx: the request was accepted and lawfully answered, and the row is the answer. Either way the patient’s attribution/marketing data is erased too: `result.engine` starts `owed` and moves to `done` asynchronously (shape: see `GET /clinic/privacy-requests`); from then on no outcome, identity or link of that contact reaches the attribution engine again. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Documents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 600
                  }
                }
              },
              "example": {
                "reason": "La paciente ejerce su derecho de supresión (Ley 21.719)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Privacy request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "17171717-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "12121212-0000-4000-8000-000000000001",
                    "kind": "export",
                    "status": "completed",
                    "requested_by_user_id": "11111111-0000-4000-8000-000000000001",
                    "requested_reason": "La paciente ejerce su derecho de acceso (Ley 21.719)",
                    "started_at": "2026-09-24T18:20:04.001Z",
                    "completed_at": "2026-09-24T18:20:07.430Z",
                    "size_bytes": 148223,
                    "token_expires_at": "2026-10-01T18:20:07.430Z",
                    "result": {
                      "documents": 3,
                      "photos": 1,
                      "notes": 6,
                      "consents": 2
                    },
                    "retention_until": "2026-10-01",
                    "refusal_reason": null,
                    "error": null,
                    "created_at": "2026-09-24T18:20:04.001Z",
                    "updated_at": "2026-09-24T18:20:07.430Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients/{id}/prescriptions": {
      "get": {
        "summary": "The recetas on one patient’s ficha",
        "description": "`data` holds the native recetas, newest first, each with its items, its status (`draft` | `signed`), `last_send` (the newest delivered send of a signed receta — `channel`, a masked `recipient_hint`, `sent_at`, `sent_by` — or null) and — when a newer receta corrected it — `replaced_by`. `documents` holds the patient’s OTHER recetas: clinic documents of kind `receta` that no native receta owns (imported from the previous system, or uploaded by hand), so both kinds read together in one disclosure. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Recetas and receta documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "01929b7e-0000-7000-8000-000000000101",
                        "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
                        "encounter_id": null,
                        "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                        "kind": "simple",
                        "notes": "Tomar con alimentos.",
                        "replaces_prescription_id": null,
                        "replace_reason": null,
                        "status": "signed",
                        "signed_at": "2026-11-02T13:42:00.000Z",
                        "prescriber": {
                          "id": "01929b7e-0000-7000-8000-000000000301",
                          "nombre": "Profesional Ejemplo",
                          "especialidad": "Endodoncia"
                        },
                        "document_id": "01929b7e-0000-7000-8000-000000000401",
                        "replaced_by": null,
                        "items": [
                          {
                            "id": "01929b7e-0000-7000-8000-000000000501",
                            "position": 1,
                            "medication": "Amoxicilina 500 mg",
                            "dose": "1 comprimido",
                            "instructions": "Cada 8 horas durante 7 días."
                          }
                        ]
                      }
                    ],
                    "documents": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Write a receta (and optionally sign it)",
        "description": "Free-text items (medication, dose, instructions; 1–20), the prescribing professional (required), an optional atención and general notes. Born a `draft` unless `sign: true`, which signs it in the same call — the ordinary path for a dentist. Only a member signs: an API key may prepare a draft and is refused `sign: true`. To CORRECT a signed receta, write a new one with `replaces_prescription_id` and a mandatory `replace_reason`; a receta is replaced at most once. Controlled and retained recetas are issued outside Vitrina. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "prescriber_professional_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "medication": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300,
                          "example": "Amoxicilina 500 mg"
                        },
                        "dose": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 200,
                          "example": "1 comprimido"
                        },
                        "instructions": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 1000,
                          "example": "Cada 8 horas durante 7 días."
                        }
                      },
                      "required": [
                        "medication",
                        "instructions"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20
                  },
                  "replaces_prescription_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "replace_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "sign": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "prescriber_professional_id",
                  "items"
                ],
                "additionalProperties": false
              },
              "example": {
                "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                "notes": "Tomar con alimentos.",
                "items": [
                  {
                    "medication": "Amoxicilina 500 mg",
                    "dose": "1 comprimido",
                    "instructions": "Cada 8 horas durante 7 días."
                  }
                ],
                "sign": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Receta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929b7e-0000-7000-8000-000000000101",
                    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
                    "encounter_id": null,
                    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                    "kind": "simple",
                    "notes": "Tomar con alimentos.",
                    "replaces_prescription_id": null,
                    "replace_reason": null,
                    "status": "signed",
                    "signed_at": "2026-11-02T13:42:00.000Z",
                    "prescriber": {
                      "id": "01929b7e-0000-7000-8000-000000000301",
                      "nombre": "Profesional Ejemplo",
                      "especialidad": "Endodoncia"
                    },
                    "document_id": "01929b7e-0000-7000-8000-000000000401",
                    "replaced_by": null,
                    "items": [
                      {
                        "id": "01929b7e-0000-7000-8000-000000000501",
                        "position": 1,
                        "medication": "Amoxicilina 500 mg",
                        "dose": "1 comprimido",
                        "instructions": "Cada 8 horas durante 7 días."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/prescriptions/{id}": {
      "get": {
        "summary": "One receta",
        "description": "The receta with its items and status. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Receta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929b7e-0000-7000-8000-000000000101",
                    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
                    "encounter_id": null,
                    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                    "kind": "simple",
                    "notes": "Tomar con alimentos.",
                    "replaces_prescription_id": null,
                    "replace_reason": null,
                    "status": "signed",
                    "signed_at": "2026-11-02T13:42:00.000Z",
                    "prescriber": {
                      "id": "01929b7e-0000-7000-8000-000000000301",
                      "nombre": "Profesional Ejemplo",
                      "especialidad": "Endodoncia"
                    },
                    "document_id": "01929b7e-0000-7000-8000-000000000401",
                    "replaced_by": null,
                    "items": [
                      {
                        "id": "01929b7e-0000-7000-8000-000000000501",
                        "position": 1,
                        "medication": "Amoxicilina 500 mg",
                        "dose": "1 comprimido",
                        "instructions": "Cada 8 horas durante 7 días."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "patch": {
        "summary": "Edit a DRAFT receta",
        "description": "Drafts only. `items`, when present, replaces every item. A signed receta answers 400 with `reason: signed_immutable` — correct it with a new receta that replaces it. The database refuses the edit too. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "prescriber_professional_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "encounter_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "medication": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300,
                          "example": "Amoxicilina 500 mg"
                        },
                        "dose": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 200,
                          "example": "1 comprimido"
                        },
                        "instructions": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 1000,
                          "example": "Cada 8 horas durante 7 días."
                        }
                      },
                      "required": [
                        "medication",
                        "instructions"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "notes": "Tomar con alimentos. Control en 7 días.",
                "items": [
                  {
                    "medication": "Amoxicilina 500 mg",
                    "dose": "1 comprimido",
                    "instructions": "Cada 8 horas durante 7 días."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Receta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929b7e-0000-7000-8000-000000000101",
                    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
                    "encounter_id": null,
                    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                    "kind": "simple",
                    "notes": "Tomar con alimentos.",
                    "replaces_prescription_id": null,
                    "replace_reason": null,
                    "status": "signed",
                    "signed_at": "2026-11-02T13:42:00.000Z",
                    "prescriber": {
                      "id": "01929b7e-0000-7000-8000-000000000301",
                      "nombre": "Profesional Ejemplo",
                      "especialidad": "Endodoncia"
                    },
                    "document_id": "01929b7e-0000-7000-8000-000000000401",
                    "replaced_by": null,
                    "items": [
                      {
                        "id": "01929b7e-0000-7000-8000-000000000501",
                        "position": 1,
                        "medication": "Amoxicilina 500 mg",
                        "dose": "1 comprimido",
                        "instructions": "Cada 8 horas durante 7 días."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/prescriptions/{id}/discard": {
      "post": {
        "summary": "Discard a DRAFT receta",
        "description": "Deletes an unsigned draft. A signed receta is never deleted (400, `reason: signed_immutable`). Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Discarded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929b7e-0000-7000-8000-000000000101",
                    "discarded": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/prescriptions/{id}/sign": {
      "post": {
        "summary": "Sign (issue) a receta",
        "description": "Freezes the receta, its items and the prescriber’s details; renders the PDF with the clinic’s branding and files it on the ficha as a clinic document of kind `receta` — in one transaction with the signature. Signed by the prescriber in person: the member’s e-mail must be the prescribing professional’s e-mail on the roster (403 `reason: prescriber_not_signer`); an API key is refused. Signing twice answers 400 `reason: already_signed`; a draft edited while the paper was being drawn answers 400 `reason: draft_changed` and nothing is filed. The filed PDF cannot be deleted by hand (`receta_paper_locked`). Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Signed receta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "01929b7e-0000-7000-8000-000000000101",
                    "clinic_patient_id": "01929b7e-0000-7000-8000-000000000201",
                    "encounter_id": null,
                    "prescriber_professional_id": "01929b7e-0000-7000-8000-000000000301",
                    "kind": "simple",
                    "notes": "Tomar con alimentos.",
                    "replaces_prescription_id": null,
                    "replace_reason": null,
                    "status": "signed",
                    "signed_at": "2026-11-02T13:42:00.000Z",
                    "prescriber": {
                      "id": "01929b7e-0000-7000-8000-000000000301",
                      "nombre": "Profesional Ejemplo",
                      "especialidad": "Endodoncia"
                    },
                    "document_id": "01929b7e-0000-7000-8000-000000000401",
                    "replaced_by": null,
                    "items": [
                      {
                        "id": "01929b7e-0000-7000-8000-000000000501",
                        "position": 1,
                        "medication": "Amoxicilina 500 mg",
                        "dose": "1 comprimido",
                        "instructions": "Cada 8 horas durante 7 días."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/prescriptions/{id}/pdf": {
      "get": {
        "summary": "The receta as a PDF",
        "description": "A signed receta answers with the very bytes filed at signing (logged as `print`). A draft answers with a preview drawn now, marked «BORRADOR · SIN VALIDEZ COMO RECETA», stored nowhere. `Cache-Control: no-store`. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The receta",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/prescriptions/{id}/send": {
      "post": {
        "summary": "Send a signed receta to the patient (WhatsApp or email)",
        "description": "Only a SIGNED receta that no newer receta has replaced (409 `not_signed` | `replaced`). What leaves Vitrina is a LINK, never the PDF: the message names the patient’s first name and the clinic, and never a medication. The link is a signed, expiring capability (14 days) whose receta row is re-read on every open. `channel: \"whatsapp\"` goes through the patient’s own conversation and only while Meta’s 24 h window is open (409 `whatsapp_window_closed` | `no_whatsapp_thread` | `no_phone`) — the same rule as the presupuesto send; `channel: \"email\"` needs an address on the ficha (409 `no_email`). A failed delivery is 409 `delivery_failed` and records no send. The send is recorded in the access log as action `share` (who, when, channel, a masked recipient hint) and in the audit log. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "email"
                    ],
                    "description": "whatsapp needs an open 24 h window with the patient; email needs an address on the ficha."
                  }
                },
                "required": [
                  "channel"
                ],
                "additionalProperties": false
              },
              "example": {
                "channel": "whatsapp"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "prescription_id": "01929b7e-0000-7000-8000-000000000101",
                    "channel": "whatsapp",
                    "recipient_hint": "+56•••••4567",
                    "sent_at": "2026-11-02T14:05:00.000Z",
                    "link_expires_at": "2026-11-16T14:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/prescriptions/{id}/sends": {
      "get": {
        "summary": "When and how a receta was sent to the patient",
        "description": "Delivered sends of this receta, newest first: channel, a masked recipient hint, when, and who sent it. Requires `clinic_record:read` (reads) or `clinic_record:write` (writes) AND the per-patient PHI gate. Every call writes a `clinic_record_access_log` row; a call that cannot write its access event fails.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Sends",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "channel": "whatsapp",
                      "recipient_hint": "+56•••••4567",
                      "sent_at": "2026-11-02T14:05:00.000Z",
                      "sent_by": "dra.perez@clinica.example"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/duplicates": {
      "get": {
        "summary": "Possible duplicate patients, grouped with the reason",
        "description": "Scans the LIVE roster (tombstones excluded) for three patterns: the same document, the same normalised phone, and the same name + date of birth. A RUT is canonicalised through the one legal-identity normaliser and a passport is never folded through it, so a mistyped RUT groups with nothing rather than colliding with a real passport. One group per candidate SET (a pair matching on two rules is one card listing both reasons), strongest reason first. `vendor_blocked: true` means every member carries a Medilink/Reservo id and the merge has to happen there first. `truncated` says the roster is larger than one scan.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "responses": {
          "200": {
            "description": "Duplicate candidate groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "groups": [],
                    "scanned": 2,
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/merge": {
      "post": {
        "summary": "Merge a patient into another (tombstone, never a delete)",
        "description": "`{id}` is the ficha that STOPS being used; `into_patient_id` is the one that survives. Repoints the links, the waitlist entries and the family bonds, fills the survivor's BLANK fields from the loser (never overwrites), writes a `clinic_patient_merge` tombstone with a snapshot of the losing ficha plus who/when/why, and marks the loser `merged_into` — which keeps its row and its id resolvable (Ley 21.719 + the 15-year retention floor, §H.3). REFUSES 409 `vendor_identity_conflict` when the loser carries a Medilink/Reservo id the survivor does not share: the next poll would re-create it, so that merge belongs in the vendor. Also 409 for an already-merged ficha on either side.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "into_patient_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 500
                  }
                },
                "required": [
                  "into_patient_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "into_patient_id": "42db9ec9-0000-4000-8000-000000000001",
                "reason": "Misma persona, cargada dos veces"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Merge result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "merge_id": "14091a9f-0000-4000-8000-000000000001",
                    "survivor": {
                      "id": "42db9ec9-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "rut": "22.222.222-2",
                      "nombre": "Tomás",
                      "apellidos": "Riquelme Paz",
                      "email": null,
                      "phone": "+56911112222",
                      "birthdate": null,
                      "nombre_social": null,
                      "prevision": "Isapre",
                      "sexo": null,
                      "numero_ficha": null,
                      "ciudad": null,
                      "region": null,
                      "comuna": null,
                      "direccion": null,
                      "description": null,
                      "enabled": true,
                      "data": {},
                      "synced_at": null,
                      "merged_into": null,
                      "merged_at": null,
                      "created_at": "2026-09-22T22:21:14.418Z",
                      "updated_at": "2026-09-22T22:21:14.418Z"
                    },
                    "loser_id": "c6bc29c3-0000-4000-8000-000000000001",
                    "moved": {
                      "healthatom_patient_link": 0,
                      "waitlist_entry": 0,
                      "clinic_patient_relationship": 0,
                      "clinic_patient_relationship.related_patient_id": 0,
                      "clinic_pack_purchase": 0,
                      "clinic_encounter": 0,
                      "clinic_encounter_note": 0,
                      "clinic_history_item": 0,
                      "clinic_clinical_flag": 0,
                      "clinic_form_response": 0,
                      "clinic_consent": 0,
                      "clinic_record_photo": 0,
                      "clinic_document": 0,
                      "clinic_patient_agreement": 0,
                      "clinic_appointment_series": 0,
                      "clinic_specialty_chart": 0,
                      "clinic_budget": 0,
                      "clinic_treatment_plan": 0,
                      "clinic_tax_document": 0
                    },
                    "left_on_tombstone": {
                      "clinic_patient_relationship": 0,
                      "clinic_patient_agreement": 0,
                      "clinic_specialty_chart": 0
                    },
                    "filled_fields": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients/{id}/relationships": {
      "get": {
        "summary": "The family of one patient (apoderados, cargas, pareja)",
        "description": "Every contact bonded to this ficha, primary first. Each bond carries two SEPARATE authorities: `can_book` (may act on the agenda) and `can_read_record` (⚠ the PHI gate — may be told what is in the clinical record). `expired: true` marks a bond whose `expires_at` has passed; every gate already treats it as no bond at all.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Patient relationships",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "6a73a912-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "related_patient_id": null,
                        "role": "titular",
                        "is_primary": true,
                        "can_book": true,
                        "can_read_record": false,
                        "verified_method": "phone_match",
                        "verified_at": "2026-09-22T22:21:14.390Z",
                        "expires_at": null,
                        "note": null,
                        "created_by": null,
                        "created_at": "2026-09-22T22:21:14.389Z",
                        "updated_at": "2026-09-22T22:21:14.389Z",
                        "contact_name": "María José Fuentes Lagos",
                        "contact_phone": "+56987654321",
                        "patient_name": "María José Fuentes Lagos",
                        "expired": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Add somebody to a patient’s family",
        "description": "Creates one bond between this ficha and a contact. 409 `relationship_exists` when the pair is already recorded (edit it instead — quietly resetting a PHI flag is not what the caller asked for). Granting `can_read_record: true` additionally requires the `clinic_record:write` scope; everything else about a family is clinic administration and needs only `clinic:write`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "related_patient_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "titular",
                      "apoderado",
                      "tutor",
                      "carga",
                      "pareja",
                      "otro"
                    ]
                  },
                  "is_primary": {
                    "type": "boolean"
                  },
                  "can_book": {
                    "type": "boolean"
                  },
                  "can_read_record": {
                    "type": "boolean"
                  },
                  "verified_method": {
                    "type": "string",
                    "enum": [
                      "phone_match",
                      "id_challenge",
                      "manual",
                      "document"
                    ]
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "contact_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "role": "titular",
                "is_primary": true,
                "can_book": true,
                "verified_method": "phone_match"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Relationship",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a73a912-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "related_patient_id": null,
                    "role": "titular",
                    "is_primary": true,
                    "can_book": true,
                    "can_read_record": false,
                    "verified_method": "phone_match",
                    "verified_at": "2026-09-22T22:21:14.390Z",
                    "expires_at": null,
                    "note": null,
                    "created_by": null,
                    "created_at": "2026-09-22T22:21:14.389Z",
                    "updated_at": "2026-09-22T22:21:14.389Z",
                    "contact_name": "María José Fuentes Lagos",
                    "contact_phone": "+56987654321",
                    "patient_name": "María José Fuentes Lagos",
                    "expired": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/contacts/{contactId}/relationships": {
      "get": {
        "summary": "Every patient one contact acts for",
        "description": "The conversation side of the same table, and what the contact ficha’s «Familia y apoderados» block renders. A contact with no clinical bonds answers an empty list, not a 404.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contactId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact relationships",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "6a73a912-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "related_patient_id": null,
                        "role": "titular",
                        "is_primary": true,
                        "can_book": true,
                        "can_read_record": false,
                        "verified_method": "phone_match",
                        "verified_at": "2026-09-22T22:21:14.390Z",
                        "expires_at": null,
                        "note": null,
                        "created_by": null,
                        "created_at": "2026-09-22T22:21:14.389Z",
                        "updated_at": "2026-09-22T22:21:14.389Z",
                        "contact_name": "María José Fuentes Lagos",
                        "contact_phone": "+56987654321",
                        "patient_name": "María José Fuentes Lagos",
                        "expired": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/relationships/{id}": {
      "delete": {
        "summary": "Remove a family bond",
        "description": "Hard delete, deliberately: a relationship is a live PERMISSION, not a clinical fact. The record of who ACCESSED a ficha is `clinic_record_event`, which is append-only and untouched by this.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "patch": {
        "summary": "Edit one family bond",
        "description": "Partial update. Promoting a bond to `is_primary` demotes the incumbent first (the partial unique index allows exactly one per patient). Raising `can_read_record` needs `clinic_record:write`; lowering it never does — revoking access is always safe.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "related_patient_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "titular",
                      "apoderado",
                      "tutor",
                      "carga",
                      "pareja",
                      "otro"
                    ]
                  },
                  "is_primary": {
                    "type": "boolean"
                  },
                  "can_book": {
                    "type": "boolean"
                  },
                  "can_read_record": {
                    "type": "boolean"
                  },
                  "verified_method": {
                    "type": "string",
                    "enum": [
                      "phone_match",
                      "id_challenge",
                      "manual",
                      "document"
                    ]
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "can_read_record": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Relationship",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a73a912-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000002",
                    "related_patient_id": null,
                    "role": "apoderado",
                    "is_primary": false,
                    "can_book": true,
                    "can_read_record": false,
                    "verified_method": "id_challenge",
                    "verified_at": "2026-09-22T22:21:17.284Z",
                    "expires_at": null,
                    "note": "Presentó cédula en el mesón",
                    "created_by": null,
                    "created_at": "2026-09-22T22:21:17.283Z",
                    "updated_at": "2026-09-22T22:21:17.298Z",
                    "contact_name": "Carmen Lagos Pinto",
                    "contact_phone": "+56955554444",
                    "patient_name": "María José Fuentes Lagos",
                    "expired": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/auto-links": {
      "get": {
        "summary": "Automatic contact links of a patient (Ads identity only)",
        "description": "The contacts Vitrina matched to this ficha AUTOMATICALLY for Ads outcomes: an E.164 phone or an exact email held by exactly one patient and exactly one contact of the clinic, the patient an adult with a plausible birth date and not a carga, and no family bond or booking link on either side. It is identity for Ads only — it grants no booking and no access to the clinical record, and a family bond or a booking link always outranks it. Each row: `id`, `clinic_patient_id`, `contact_id`, `patient_name`, `contact_name`, `status` (`live` | `revoked` — a run found it no longer unique or ineligible — | `rejected` — a person said it is not this person, never re-opened), `method` (`phone_unique` | `email_unique`: which identifier was held by exactly one patient and one contact; `chat_rut`: the patient's RUT typed by exactly one contact in its own ad chat, corroborated by the typed email or by the ficha appearing after it — the value itself is never stored), `matched_at`, `run_id` (the matching run, provenance), `revoked_at`, `revoked_reason`, `sent_count` / `last_sent_at` (Ads outcomes sent under the pair). Live first.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Patient auto-links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "a7a7a7a7-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "patient_name": "María José Fuentes Lagos",
                        "contact_name": "María José Fuentes Lagos",
                        "status": "live",
                        "method": "phone_unique",
                        "matched_at": "2026-09-27T14:10:02.118Z",
                        "run_id": "a8a8a8a8-0000-4000-8000-000000000001",
                        "revoked_at": null,
                        "revoked_reason": null,
                        "sent_count": 3,
                        "last_sent_at": "2026-09-28T12:02:40.551Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/contacts/{contactId}/auto-links": {
      "get": {
        "summary": "Patients automatically linked to a contact (Ads identity only)",
        "description": "The contact side of the same automatic links — what the contact ficha shows as «Vinculado automáticamente». An empty list, not a 404, when there is none. Each row: `id`, `clinic_patient_id`, `contact_id`, `patient_name`, `contact_name`, `status` (`live` | `revoked` — a run found it no longer unique or ineligible — | `rejected` — a person said it is not this person, never re-opened), `method` (`phone_unique` | `email_unique`: which identifier was held by exactly one patient and one contact; `chat_rut`: the patient's RUT typed by exactly one contact in its own ad chat, corroborated by the typed email or by the ficha appearing after it — the value itself is never stored), `matched_at`, `run_id` (the matching run, provenance), `revoked_at`, `revoked_reason`, `sent_count` / `last_sent_at` (Ads outcomes sent under the pair). Live first.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contactId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact auto-links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "a7a7a7a7-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "patient_name": "María José Fuentes Lagos",
                        "contact_name": "María José Fuentes Lagos",
                        "status": "live",
                        "method": "phone_unique",
                        "matched_at": "2026-09-27T14:10:02.118Z",
                        "run_id": "a8a8a8a8-0000-4000-8000-000000000001",
                        "revoked_at": null,
                        "revoked_reason": null,
                        "sent_count": 3,
                        "last_sent_at": "2026-09-28T12:02:40.551Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/auto-links/{id}/reject": {
      "post": {
        "summary": "«No es esta persona» — reject an automatic link for good",
        "description": "Marks the pair `rejected`: no later matching run re-opens it, and the Ads outcomes sent under it that have not left Vitrina yet are withheld (`withheld_outcomes` says how many). What already reached the ad platforms is not recalled. Idempotent. Answers the row. 404 when the id is not this clinic’s.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rejected auto-link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "patient_name": "María José Fuentes Lagos",
                    "contact_name": "María José Fuentes Lagos",
                    "status": "rejected",
                    "method": "phone_unique",
                    "matched_at": "2026-09-27T14:10:02.118Z",
                    "run_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "revoked_at": "2026-09-28T15:31:09.004Z",
                    "revoked_reason": "rejected",
                    "sent_count": 3,
                    "last_sent_at": "2026-09-28T12:02:40.551Z",
                    "withheld_outcomes": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/insights/produccion": {
      "get": {
        "summary": "What was performed and what was collected",
        "description": "Producción, aggregates only. A Dentalink / Medilink clinic (revenue mirror on, `basis: mirror_lines`) reads the vendor's own plan lines marked realizado, by fecha de realización, at the line's total — Dentalink's «Ventas (acciones realizadas)», evaluación included, $0 lines out; `collected_clp` is every live payment received in the window (`null` with `professional_id`: a payment names no professional). Otherwise only ATTENDED citas count, valued from the cita's frozen `clinic_appointment_service` lines (a native clinic) or the catálogo's current price for the prestación the mirror names; `lines_without_price` names the hole, and `collected_clp` is what the ledger allocated to those citas. `by_professional` and the `professional_id` filter follow who PERFORMED the work, not who quoted the plan. Requires `clinic_money:read`; a caller holding only `clinic_insights:read` reads the last closed months with the minimum-count floor (`suppression`). Accepts `from` / `to` (bare `YYYY-MM-DD`, `to` INCLUSIVE), `location_id` and `professional_id`. The default window is the 90 days ending today in the clinic's own zone. A `professional_id` that is not yours is a 404, never an empty report. Also reachable with `clinic_insights:read` — the aggregates-only «Consultor» role (spec #2441 D11) — in addition to the scope named above. A caller WITHOUT the report’s own scope never receives a patient-identifiable row (`inasistencias.citas` and `packs.expiring` come back empty with `aggregate_only: true` and `rows_withheld` naming the field) and reads the minimum-count floor: always the last 3 closed calendar months with no professional or sucursal filter, whatever was asked (`suppression.filters_ignored`); a count or CLP resting on 1–4 cases is `null`; a rate over fewer than 5 cases, or whose numerator is withheld, is `null`; list rows that add up to a shown total get complementary suppression and leave the list (`suppression.withheld_rows`); per-professional/prestación/weekday outcome rows and per-channel confirmation rows carry rates only; producción serves no per-prestación rows (they nest inside the categorías); clinic-wide cash over whole months stays while its slices are floored. `suppression.min_count` is the floor.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window, inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window, inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One profesional. A foreign id is a 404, never an empty report."
            },
            "required": false,
            "description": "One profesional. A foreign id is a 404, never an empty report.",
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic production report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicProduccionReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-09-01",
                    "to": "2026-09-30",
                    "location_id": null,
                    "professional_id": null,
                    "basis": "mirror_lines",
                    "totals": {
                      "attended": 612,
                      "lines": 699,
                      "visits": 431,
                      "lines_without_price": 0,
                      "performed_clp": 57179516,
                      "collected_clp": 57172232,
                      "collected_basis": "mirror_payment.received"
                    },
                    "by_professional": [
                      {
                        "key": "00000000-0000-4000-8000-0000000000a1",
                        "label": "Ana Rojas",
                        "appointments": 141,
                        "lines": 168,
                        "performed_clp": 9623135
                      }
                    ],
                    "by_service": [
                      {
                        "key": "00000000-0000-4000-8000-0000000000b3",
                        "label": "Endodoncia molar",
                        "appointments": 38,
                        "lines": 38,
                        "performed_clp": 9880000
                      }
                    ],
                    "by_category": [
                      {
                        "key": "Endodoncia",
                        "label": "Endodoncia",
                        "appointments": 52,
                        "lines": 61,
                        "performed_clp": 12400000
                      }
                    ],
                    "months": [
                      {
                        "month": "2026-09",
                        "attended": 612,
                        "performed_clp": 57179516,
                        "collected_clp": 57172232
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/ingresos": {
      "get": {
        "summary": "Ingresos: producción, presupuestado and recaudación by day",
        "description": "«Ingresos» (D56). A NATIVE clinic reads Vitrina's own ledger (`source.kind: native`, #5367): producción = the service lines of attended citas by the cita's day, presupuestado = presupuesto lineages first sent that day at the latest version's total, recaudación = live payments by fecha de pago, acceptance = the folio's explicit acceptance. A Dentalink / Medilink clinic with the revenue mirror on reads the mirror (`source.kind: mirror`): producción (performed plan lines by fecha de realización), presupuestado (plans issued, header total, evaluación included, as the vendor's panel) and recaudación (live payments by fecha de recepción). Anything else is `state: unavailable`. Each flow per day is split `nuevos` (the patient's primera visita starts in that month) vs `tratamiento`. `hoy` carries today's three numbers whatever the window. `by_professional`: producción by PERFORMER; presupuestos, aceptación and monto aceptado by the QUOTING professional under Números comerciales' acceptance rule ($0 and evaluación-only presupuestos out); abonado = paid so far on the plans issued in the period; new patients presented / accepted. Default window: the current month to today. With `professional_id` recaudación is `null` (a payment names no professional). Every day and the totals also carry `by_origen` (D77): the three flows split Meta / Orgánico / Sin dato by the shared person-origin resolver; `origin` narrows the whole report to one Origen. Requires `clinic_money:read`, or `clinic_insights:read` (the Consultor seat, D76: the payload is aggregates only). Accepts `from` / `to` (bare `YYYY-MM-DD`, `to` INCLUSIVE), `location_id` and `professional_id`. The default window is the 90 days ending today in the clinic's own zone. A `professional_id` that is not yours is a 404, never an empty report. Also reachable with `clinic_insights:read` — the aggregates-only «Consultor» role (spec #2441 D11) — in addition to the scope named above. A caller WITHOUT the report’s own scope never receives a patient-identifiable row (`inasistencias.citas` and `packs.expiring` come back empty with `aggregate_only: true` and `rows_withheld` naming the field) and reads the minimum-count floor: always the last 3 closed calendar months with no professional or sucursal filter, whatever was asked (`suppression.filters_ignored`); a count or CLP resting on 1–4 cases is `null`; a rate over fewer than 5 cases, or whose numerator is withheld, is `null`; list rows that add up to a shown total get complementary suppression and leave the list (`suppression.withheld_rows`); per-professional/prestación/weekday outcome rows and per-channel confirmation rows carry rates only; producción serves no per-prestación rows (they nest inside the categorías); clinic-wide cash over whole months stays while its slices are floored. `suppression.min_count` is the floor.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window, inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window, inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One profesional. A foreign id is a 404, never an empty report."
            },
            "required": false,
            "description": "One profesional. A foreign id is a 404, never an empty report.",
            "name": "professional_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "meta",
                "organic",
                "unknown"
              ],
              "description": "Narrow every figure to the people of one Origen: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no contact is linked). Same resolver as the agenda's Origen filter."
            },
            "required": false,
            "description": "Narrow every figure to the people of one Origen: `meta` (came from a Meta ad), `organic` (a linked contact with no ad touch) or `unknown` («Sin dato»: no contact is linked). Same resolver as the agenda's Origen filter.",
            "name": "origin",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic Ingresos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicIngresosReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-10-01",
                    "to": "2026-10-08",
                    "today": "2026-10-08",
                    "timezone": "America/Santiago",
                    "location_id": null,
                    "professional_id": null,
                    "origin": null,
                    "state": "ready",
                    "state_reason": null,
                    "source": {
                      "kind": "mirror",
                      "synced_at": "2026-10-08T20:10:00.000Z"
                    },
                    "rules": {
                      "produccion": "Lo realizado en el box …",
                      "presupuestado": "Presupuestos creados en el día …",
                      "recaudacion": "Pagos recibidos en el día …",
                      "nuevos": "Paciente nuevo: su primera visita es en ese mismo mes …",
                      "aceptacion": "Aceptados con la misma regla de Números comerciales …",
                      "abonado": "Lo ya pagado a los presupuestos creados en el período …"
                    },
                    "hoy": {
                      "day": "2026-10-08",
                      "produccion_clp": 3670982,
                      "prestaciones": 47,
                      "presupuestado_clp": 4381028,
                      "presupuestos": 18,
                      "recaudado_clp": 3307992,
                      "pagos": 21,
                      "citas": 52
                    },
                    "totals": {
                      "produccion": {
                        "total_clp": 18626657,
                        "nuevos_clp": 412000,
                        "tratamiento_clp": 3258982,
                        "prestaciones": 47
                      },
                      "presupuestado": {
                        "total_clp": 25014395,
                        "nuevos_clp": 1072572,
                        "tratamiento_clp": 3308456,
                        "presupuestos": 18
                      },
                      "recaudacion": {
                        "total_clp": 14856381,
                        "nuevos_clp": 811084,
                        "tratamiento_clp": 2496908,
                        "pagos": 21
                      },
                      "by_origen": {
                        "meta": {
                          "produccion_clp": 1204000,
                          "presupuestado_clp": 1830000,
                          "recaudado_clp": 1020000
                        },
                        "organic": {
                          "produccion_clp": 2210982,
                          "presupuestado_clp": 2301028,
                          "recaudado_clp": 1987992
                        },
                        "unknown": {
                          "produccion_clp": 256000,
                          "presupuestado_clp": 250000,
                          "recaudado_clp": 300000
                        }
                      }
                    },
                    "days": [
                      {
                        "day": "2026-10-08",
                        "produccion": {
                          "total_clp": 3670982,
                          "nuevos_clp": 412000,
                          "tratamiento_clp": 3258982,
                          "prestaciones": 47
                        },
                        "presupuestado": {
                          "total_clp": 4381028,
                          "nuevos_clp": 1072572,
                          "tratamiento_clp": 3308456,
                          "presupuestos": 18
                        },
                        "recaudacion": {
                          "total_clp": 3307992,
                          "nuevos_clp": 811084,
                          "tratamiento_clp": 2496908,
                          "pagos": 21
                        },
                        "by_origen": {
                          "meta": {
                            "produccion_clp": 1204000,
                            "presupuestado_clp": 1830000,
                            "recaudado_clp": 1020000
                          },
                          "organic": {
                            "produccion_clp": 2210982,
                            "presupuestado_clp": 2301028,
                            "recaudado_clp": 1987992
                          },
                          "unknown": {
                            "produccion_clp": 256000,
                            "presupuestado_clp": 250000,
                            "recaudado_clp": 300000
                          }
                        }
                      }
                    ],
                    "clinic": {
                      "produccion_clp": 18626657,
                      "prestaciones": 52,
                      "presupuestos": 14,
                      "presupuestado_clp": 5120000,
                      "aceptados": 9,
                      "aceptacion_pct": 64.3,
                      "aceptado_clp": 3010000,
                      "abonado_clp": 1240000,
                      "nuevos_presentados": 5,
                      "nuevos_aceptados": 3
                    },
                    "by_professional": [
                      {
                        "professional_id": "6b1f3c2e-8a4d-4f0e-9c1b-2d7e5a9f0c31",
                        "professional_name": "Ana Rojas",
                        "produccion_clp": 3685671,
                        "prestaciones": 52,
                        "presupuestos": 14,
                        "presupuestado_clp": 5120000,
                        "aceptados": 9,
                        "aceptacion_pct": 64.3,
                        "aceptado_clp": 3010000,
                        "abonado_clp": 1240000,
                        "nuevos_presentados": 5,
                        "nuevos_aceptados": 3
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/ingresos/months": {
      "get": {
        "summary": "Ingresos: doctor × month comparison",
        "description": "The last `months` calendar months (default 6, at most 12, the current one to today): per month the clinic's producción, recaudación, presupuestos, aceptación and monto aceptado; per doctor the same by month (producción by performer, the rest by the quoting professional over the presupuestos issued that month). Requires `clinic_money:read` or `clinic_insights:read` (aggregates only).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 12,
              "description": "Calendar months to compare, the current one included (default 6)."
            },
            "required": false,
            "description": "Calendar months to compare, the current one included (default 6).",
            "name": "months",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "location_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic Ingresos by doctor and month",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicIngresosMonths"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-09-01",
                    "to": "2026-10-08",
                    "state": "ready",
                    "rules": {
                      "produccion": "Lo realizado en el box …",
                      "presupuestado": "Presupuestos creados en el día …",
                      "recaudacion": "Pagos recibidos en el día …",
                      "nuevos": "Paciente nuevo: su primera visita es en ese mismo mes …",
                      "aceptacion": "Aceptados con la misma regla de Números comerciales …",
                      "abonado": "Lo ya pagado a los presupuestos creados en el período …"
                    },
                    "months": [
                      {
                        "month": "2026-09",
                        "produccion_clp": 57179516,
                        "recaudacion_clp": 57172232,
                        "presupuestos": 278,
                        "presupuestado_clp": 112745314,
                        "aceptacion_pct": 63.1,
                        "aceptado_clp": 51093178
                      }
                    ],
                    "professionals": [
                      {
                        "professional_id": "6b1f3c2e-8a4d-4f0e-9c1b-2d7e5a9f0c31",
                        "professional_name": "Ana Rojas",
                        "months": [
                          {
                            "month": "2026-09",
                            "produccion_clp": 9623135,
                            "presupuestos": 41,
                            "presupuestado_clp": 18200000,
                            "aceptacion_pct": 68.3,
                            "aceptado_clp": 9100000
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/ingresos/days/{day}": {
      "get": {
        "summary": "Ingresos: one day opened (its plans, payments and producción)",
        "description": "The plans issued and payments received on one clinic day, each marked `nuevo` / `tratamiento`, plans with the Números comerciales verdict, plus producción per performer. Patient names only for a caller allowed to see identity (`contacts:read` + `clinic:read`, plus `clinic_patients:read` for an API credential); otherwise `patient.name` is `null`. Each row carries the Origen of its patient (`origen`). Requires `clinic_money:read`, or `clinic_insights:read`, which never gets a patient reference (`patient.id` and `patient.name` are `null`).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$"
            },
            "required": true,
            "name": "day",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "One clinic day of Ingresos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicIngresosDay"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "day": "2026-10-08",
                    "identity": true,
                    "plans": [
                      {
                        "plan_id": "1d2c3b4a-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                        "patient": {
                          "id": "7e6d5c4b-3a29-4180-9f8e-7d6c5b4a3928",
                          "name": "Paciente de ejemplo"
                        },
                        "professional_id": "6b1f3c2e-8a4d-4f0e-9c1b-2d7e5a9f0c31",
                        "professional_name": "Ana Rojas",
                        "total_clp": 640000,
                        "abonado_clp": 120000,
                        "deuda_clp": 520000,
                        "tipo": "nuevo",
                        "origen": "meta",
                        "aceptado": true
                      }
                    ],
                    "payments": [
                      {
                        "payment_id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                        "time": "11:42",
                        "patient": {
                          "id": "7e6d5c4b-3a29-4180-9f8e-7d6c5b4a3928",
                          "name": "Paciente de ejemplo"
                        },
                        "amount_clp": 120000,
                        "tipo": "nuevo",
                        "origen": "meta"
                      }
                    ],
                    "produccion": [
                      {
                        "professional_id": "6b1f3c2e-8a4d-4f0e-9c1b-2d7e5a9f0c31",
                        "professional_name": "Ana Rojas",
                        "prestaciones": 9,
                        "produccion_clp": 812000
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/resultados": {
      "get": {
        "summary": "«Tu agenda con Vitrina»: what the AI did for the diary",
        "description": "One canonical read of what Vitrina's agent did for the clinic's agenda, aggregates only (never a patient, cita or conversation id). A cita is «agendada por Vitrina» when the agent booked it in a conversation (`source = 'agent'` and no public booking page); the window is on the day the cita HAPPENS (`starts_at`), blocks and holds out. Blocks: `bookings` (count and share of the agenda, with the previous period of the same length); `money` (the value of those citas — pipeline — and the CASH their patients paid in the period from the day Vitrina booked them, read from the revenue mirror when it is on, else from the native ledger; `null` with `withheld: true` for a caller without `clinic_money:read`); `out_of_hours` (bookings made while the workspace was closed per its business hours, plus a weekday × hour heatmap; `null` when no hours are configured); `autonomy` (bookings with no team message in the conversation, from the patient's first message of the 7 days before); `time_to_book` (median and p95, first patient message → booking); `hours_returned` (AI bookings × 10 minutes, a stated rule); `recovery` (no-shows over known attendance, and no-shows / cancellations the same patient booked again within 30 days, with how many of those Vitrina booked); `confirmation` (attendance of confirmed vs unconfirmed citas, the `confirmaciones` report's own figures); `ads` (cost per cita agendada / asistida / paciente que pagó = the Ads overview's spend ÷ its counts for the same dates; `not_active` without Vitrina Ads); `reminders` (reminders that did not go out, by cause). Every block carries a printable `rule`; every rate is `null` — never 0 — without a denominator, and measured figures carry `measured` / `unknown` counts. Requires `clinic:read`. Accepts `from` / `to` (bare `YYYY-MM-DD`, `to` inclusive; default the 90 days ending today in the clinic's zone) and `location_id`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tu agenda con Vitrina",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AgendaResults"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-07-01",
                    "to": "2026-09-28",
                    "timezone": "America/Santiago",
                    "location_id": null,
                    "family": "clinic",
                    "previous_period": {
                      "from": "2026-04-02",
                      "to": "2026-06-30"
                    },
                    "bookings": {
                      "ai": 286,
                      "agenda_total": 2011,
                      "share_pct": 14.2,
                      "previous": {
                        "ai": 240,
                        "agenda_total": 1950,
                        "share_pct": 12.3
                      },
                      "rule": "Citas del período (por la fecha de la cita) que agendó el asistente de Vitrina en una conversación, ÷ todas las citas del período."
                    },
                    "money": {
                      "applies": true,
                      "withheld": false,
                      "booked_clp": 11400000,
                      "booked_priced": 271,
                      "booked_unpriced": 15,
                      "agenda_clp": 96000000,
                      "clp_share_pct": 11.9,
                      "paid_clp": 7250000,
                      "paid_patients": 164,
                      "unlinked": 4,
                      "regime": "mirror",
                      "previous": {
                        "booked_clp": 9800000,
                        "paid_clp": 6100000
                      },
                      "rule_booked": "Valor de las citas que agendó Vitrina: el precio de sus prestaciones, o el del catálogo. Es lo agendado, no lo cobrado.",
                      "rule_paid": "Pagos recibidos en el período por los pacientes que Vitrina agendó, desde el día en que los agendó."
                    },
                    "out_of_hours": {
                      "configured": true,
                      "count": 111,
                      "pct": 38.8,
                      "previous_pct": 36.1,
                      "heatmap": [
                        {
                          "weekday": 1,
                          "hour": 21,
                          "count": 9
                        },
                        {
                          "weekday": 6,
                          "hour": 11,
                          "count": 14
                        }
                      ],
                      "rule": "Citas que Vitrina agendó mientras tu negocio estaba cerrado, según tu horario de atención (incluye feriados)."
                    },
                    "autonomy": {
                      "no_human": 158,
                      "pct": 56.6,
                      "measured": 279,
                      "unknown": 7,
                      "previous_pct": 52,
                      "rule": "Citas de Vitrina en las que nadie de tu equipo escribió en la conversación antes de agendar."
                    },
                    "time_to_book": {
                      "median_seconds": 734,
                      "p95_seconds": 80100,
                      "p95_phrase": "5 de cada 100 personas tardan más de 22 h 15 min en quedar agendadas.",
                      "measured": 279,
                      "unknown": 7,
                      "previous_median_seconds": 910,
                      "rule": "Tiempo desde el primer mensaje del paciente hasta que su cita quedó agendada (la mediana)."
                    },
                    "hours_returned": {
                      "minutes_per_booking": 10,
                      "hours": 47.7,
                      "rule": "Cada cita que agenda Vitrina ahorra 10 minutos de responder y agendar a mano."
                    },
                    "recovery": {
                      "window_days": 30,
                      "known_attendance": 1063,
                      "unknown_attendance": 21,
                      "no_show_pct": 10.2,
                      "no_show": {
                        "count": 108,
                        "recovered": 37,
                        "recovered_via_vitrina": 17,
                        "recovered_pct": 34.6,
                        "via_vitrina_pct": 15.9,
                        "pending": 12,
                        "unlinked": 1
                      },
                      "cancelled": {
                        "count": 240,
                        "recovered": 26,
                        "recovered_via_vitrina": 11,
                        "recovered_pct": 10.8,
                        "via_vitrina_pct": 4.6,
                        "pending": 18,
                        "unlinked": 0
                      },
                      "rule_no_show": "Citas pasadas marcadas como «no asistió» ÷ citas con asistencia conocida (asistió + no asistió).",
                      "rule_recovered": "Una cita perdida se recupera cuando el mismo paciente agenda otra dentro de 30 días."
                    },
                    "confirmation": {
                      "applies": true,
                      "confirmed_attendance_pct": 93.1,
                      "unconfirmed_attendance_pct": 78.4,
                      "confirmed_closed": 610,
                      "unconfirmed_closed": 212,
                      "rule": "Asistencia de las citas cuyo paciente confirmó el recordatorio, frente a las que nunca respondieron."
                    },
                    "ads": {
                      "state": "active",
                      "spend_clp": 1648000,
                      "booked": 58,
                      "attended": 41,
                      "paid": 22,
                      "cost_per_booked": 28414,
                      "cost_per_attended": 40195,
                      "cost_per_paid": 74909,
                      "rule": "Inversión en anuncios del período ÷ citas agendadas, asistidas y pacientes de anuncios que pagaron, según Vitrina Ads para las mismas fechas."
                    },
                    "reminders": {
                      "applies": true,
                      "sent": 721,
                      "failed": 120,
                      "failed_pct": 14.3,
                      "by_cause": [
                        {
                          "cause": "no_whatsapp",
                          "count": 61
                        },
                        {
                          "cause": "no_phone",
                          "count": 38
                        },
                        {
                          "cause": "meta_payment_block",
                          "count": 21
                        }
                      ],
                      "rule": "Recordatorios de citas del período que no salieron, agrupados por la causa que registró el envío."
                    },
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/resultados/people": {
      "get": {
        "summary": "«Tu agenda con Vitrina»: the patients behind a count",
        "description": "The citas or patients behind ONE count of `GET /clinic/insights/resultados`, read through the same window read — so `count` equals the page's number and `people` has exactly `count` rows. `metric`: `ai_bookings` (`bookings.ai`, one row per cita), `out_of_hours` (`out_of_hours.count`, one row per cita; `count: null` without business hours), `paid_patients` (`money.paid_patients`, one row per patient with every payment since Vitrina booked them; needs `clinic_money:read`, else `withheld: true` and no list). Each row: the contact and its roster patient (`patient_link`, `patient.{id,name,rut,phone}` — the identity gate of `/ads/attributed-sales`: `contacts:read` + `clinic:read`, plus `clinic_patients:read` for an API credential), and the cita (start, booking instant, status, professional, out-of-hours). Requires `clinic:read`. Accepts the page's `from` / `to` / `location_id`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "ai_bookings",
                "out_of_hours",
                "paid_patients"
              ],
              "description": "WHICH count to list (same window / sucursal as the page): `ai_bookings` = `bookings.ai` (one row per cita), `out_of_hours` = `out_of_hours.count` (one row per cita), `paid_patients` = `money.paid_patients` (one row per patient; needs `clinic_money:read`, else `withheld`)."
            },
            "required": true,
            "description": "WHICH count to list (same window / sucursal as the page): `ai_bookings` = `bookings.ai` (one row per cita), `out_of_hours` = `out_of_hours.count` (one row per cita), `paid_patients` = `money.paid_patients` (one row per patient; needs `clinic_money:read`, else `withheld`).",
            "name": "metric",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The people behind the count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AgendaResultsPeople"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "metric": "paid_patients",
                    "from": "2026-09-08",
                    "to": "2026-10-07",
                    "location_id": null,
                    "count": 1,
                    "people": [
                      {
                        "key": "7d1c2a5e-0000-4000-8000-000000000001",
                        "patient_link": "linked",
                        "patient": {
                          "id": "7d1c2a5e-0000-4000-8000-000000000001",
                          "name": "María Ejemplo Soto",
                          "rut": "11.111.111-1",
                          "phone": "+56911111111"
                        },
                        "contact": {
                          "id": "3f9b6c10-0000-4000-8000-000000000002",
                          "display_name": "María Ejemplo",
                          "key": "0002"
                        },
                        "cita": {
                          "id": "a0c4e2b1-0000-4000-8000-000000000003",
                          "display_id": null,
                          "starts_at": "2026-09-15T13:00:00.000Z",
                          "booked_at": "2026-09-11T23:40:00.000Z",
                          "status": "completed",
                          "professional": {
                            "id": "b5d7f9a3-0000-4000-8000-000000000004",
                            "name": "Dra. Ejemplo"
                          },
                          "out_of_hours": true
                        },
                        "paid": {
                          "total": 45000,
                          "count": 1,
                          "since_day": "2026-09-11",
                          "payments": [
                            {
                              "day": "2026-09-15",
                              "amount": 45000
                            }
                          ]
                        }
                      }
                    ],
                    "truncated": false,
                    "withheld": false,
                    "people_state": "ready"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/insights/comercial": {
      "get": {
        "summary": "Commercial numbers: presupuesto acceptance, cierres, cash, botados",
        "description": "The clinic's commercial numbers, aggregates only (counts, CLP, percentages, months and professional names; never a patient). `regime` says how acceptance is read: `evidencia` when the revenue mirror is on (a non-entry line performed, or a cita booked after the presupuesto, inside its window), `explicita` otherwise (the acceptance the clinic recorded). Acceptance is by COUNT of presupuestos, one per lineage; $0 and entry-only presupuestos are out of both sides; the window is 60 days (90 for ortodoncia and implantología); the rate goes to the professional who issued the presupuesto. Presupuestos ISSUED in the window form the acceptance cohort (`cohorts` by issue month with 30- and 60-day reads). A cierre is the first payment on an accepted presupuesto: once per presupuesto, dated on that payment, valued at the full accepted value. `headline` is the cash collected in the window against the previous window of the same length. `first_visit` (new patients who paid more than the evaluación the day of their first attended cita; `null` with `reason` when no evaluación is priced), `second_visit` (accepted presupuestos captured after the issue day) and `botados` (two lists — «nunca aceptó» and «quedó a medias», 90 days without cita or pago and no future cita, over the presupuestos of the last 12 months, per issuing professional) carry `formula: mentores_2026_09_29`. An acceptance proven by a cita stays accepted when the cita is later cancelled (`cita_only` counts them). `acceptance.reference` and each `by_professional[].reference` place the rate on the mentors' «rango de referencia del programa» (the dentist's especialidad range, else general; `null` when none). Every KPI carries its `formula` and a printable `rule`; a rate with no denominator is `null`, never 0. `state` is `empty`, `settling` (presupuesto lines still being read; `state_reason` says so) or `ready`. Requires `clinic_money:read` (or `clinic_insights:read`, which reads the same aggregates). Accepts `from` / `to` (bare `YYYY-MM-DD`, `to` INCLUSIVE; default the 90 days ending today in the clinic's zone), `location_id` and `professional_id`. With `professional_id`, cash (`headline`, `months[].collected_clp`) is `null`: a payment belongs to the patient, not to a professional. A caller WITHOUT `clinic_money:read` always reads one fixed window — the last 3 closed calendar months — whatever range or filter it asks (`suppression.filters_ignored`), with every count or CLP resting on 1–4 cases `null`, rates over fewer than 5 cases `null`, complementary suppression over professionals, cohorts and botados lists, and month rows carrying only cash.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window, inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window, inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One profesional. A foreign id is a 404, never an empty report."
            },
            "required": false,
            "description": "One profesional. A foreign id is a 404, never an empty report.",
            "name": "professional_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic commercial numbers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicComercialReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-06-01",
                    "to": "2026-08-29",
                    "timezone": "America/Santiago",
                    "location_id": null,
                    "professional_id": null,
                    "state": "ready",
                    "state_reason": null,
                    "regime": "evidencia",
                    "regime_label": "Aceptación por evidencia",
                    "regime_rule": "Leído desde tu software de agenda: un presupuesto cuenta como aceptado cuando se realizó una de sus prestaciones o el paciente quedó con una cita agendada después de recibirlo, aunque después la cancele.",
                    "source": {
                      "kind": "mirror",
                      "synced_at": "2026-08-30T03:10:00.000Z"
                    },
                    "formulas": {
                      "acceptance": "mentores_2026_09_25",
                      "cierre": "cierre_2026_09_25",
                      "first_visit": "mentores_2026_09_29",
                      "second_visit": "mentores_2026_09_29",
                      "botados": "mentores_2026_09_29"
                    },
                    "headline": {
                      "collected_clp": 48500000,
                      "previous_collected_clp": 44100000,
                      "delta_pct": 10,
                      "previous_from": "2026-03-03",
                      "previous_to": "2026-05-31",
                      "rule": "Lo cobrado en el período: todos los pagos recibidos, sin los anulados. Se compara con el período anterior del mismo largo."
                    },
                    "acceptance": {
                      "presented": 420,
                      "accepted": 231,
                      "rate": 55,
                      "same_day": 118,
                      "later": 113,
                      "cita_only": 14,
                      "pending": 36,
                      "presented_clp": 142000000,
                      "accepted_clp": 71300000,
                      "rate_30d": 47.2,
                      "rate_60d": 53.8,
                      "formula": "mentores_2026_09_25",
                      "window_days": 60,
                      "long_window_days": 90,
                      "rule": "Presupuestos aceptados ÷ presupuestos presentados, cada presupuesto una vez.",
                      "reference": {
                        "label": "Rango de referencia del programa",
                        "especialidad": null,
                        "band": "malo",
                        "note": null,
                        "muy_malo_upper_pct": 40,
                        "malo_upper_pct": 60,
                        "regular_upper_pct": 70,
                        "excelente_lower_pct": 90
                      }
                    },
                    "cierres": {
                      "count": 180,
                      "value_clp": 58900000,
                      "accepted_without_payment": 51,
                      "formula": "cierre_2026_09_25",
                      "rule": "Un cierre es el primer pago de un presupuesto aceptado."
                    },
                    "cohorts": [
                      {
                        "month": "2026-06",
                        "presented": 140,
                        "accepted": 80,
                        "rate": 57.1,
                        "same_day": 41,
                        "later": 39,
                        "cita_only": 2,
                        "pending": 0,
                        "presented_clp": 47000000,
                        "accepted_clp": 24800000,
                        "rate_30d": 49.3,
                        "rate_60d": 56.4,
                        "mature": true
                      }
                    ],
                    "by_professional": [
                      {
                        "professional_id": "0f3c2a9e-5b1d-4c7e-9a42-6d8e1f2b3c4d",
                        "professional_name": "Dra. Camila Soto",
                        "presented": 96,
                        "accepted": 61,
                        "rate": 63.5,
                        "same_day": 30,
                        "later": 31,
                        "cita_only": 3,
                        "pending": 8,
                        "presented_clp": 33400000,
                        "accepted_clp": 19900000,
                        "cierres": 47,
                        "cierres_clp": 15100000,
                        "reference": {
                          "label": "Rango de referencia del programa",
                          "especialidad": null,
                          "band": "regular",
                          "note": null,
                          "muy_malo_upper_pct": 40,
                          "malo_upper_pct": 60,
                          "regular_upper_pct": 70,
                          "excelente_lower_pct": 90
                        }
                      }
                    ],
                    "unassigned_presented": 0,
                    "unassigned_cierres": 0,
                    "months": [
                      {
                        "month": "2026-06",
                        "accepted_clp": 24100000,
                        "produced_clp": 19700000,
                        "collected_clp": 16200000,
                        "cierres": 62,
                        "cierres_clp": 20300000
                      }
                    ],
                    "first_visit": {
                      "formula": "mentores_2026_09_29",
                      "patients": 210,
                      "paid": 97,
                      "rate": 46.2,
                      "evaluation_price_clp": 34990,
                      "reason": null,
                      "rule": "De los pacientes nuevos cuya primera cita atendida cae en el período, la parte que ese día pagó más que la evaluación ($34.990)."
                    },
                    "second_visit": {
                      "formula": "mentores_2026_09_29",
                      "eligible": 231,
                      "converted": 113,
                      "rate": 48.9,
                      "rule": "De los presupuestos aceptados del período, la parte que se aceptó en una visita posterior y no el mismo día en que se entregó."
                    },
                    "botados": {
                      "unaccepted": {
                        "count": 88,
                        "clp": 31200000
                      },
                      "accepted_unscheduled": {
                        "count": 0,
                        "clp": 0
                      },
                      "mid_treatment_no_future_cita": {
                        "count": 23,
                        "clp": 9800000
                      },
                      "by_professional": [
                        {
                          "professional_id": "0f3c2a9e-5b1d-4c7e-9a42-6d8e1f2b3c4d",
                          "professional_name": "Dra. Camila Soto",
                          "unaccepted": {
                            "count": 21,
                            "clp": 7400000
                          },
                          "mid_treatment_no_future_cita": {
                            "count": 6,
                            "clp": 2900000
                          }
                        }
                      ],
                      "formula": "mentores_2026_09_29",
                      "basis": "live_last_12_months",
                      "idle_days": 90,
                      "total": {
                        "count": 111,
                        "clp": 41000000
                      },
                      "focus": "unaccepted",
                      "rule": "Presupuestos de los últimos 12 meses sin cita futura: «nunca aceptó» o «quedó a medias» tras 90 días sin cita ni pago."
                    },
                    "coverage": {
                      "plans_without_lines": 0,
                      "excluded_zero": 301,
                      "excluded_entry_only": 92
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/targets/{month}": {
      "get": {
        "summary": "The month's Meta mensual and its progress",
        "description": "Requires `clinic_money:read` or `clinic_insights:read` (the Consultor seat: the payload is aggregates only, D76). The owner's four targets for the month (primeras visitas booked, cierres, cobrado = recaudación in CLP, producción in CLP; `null` = not set) with what has been achieved so far: primeras visitas = citas created in the month whose Tipo de agendamiento is Nuevo or Nuevo · reagenda; cierres as Números comerciales counts them; cobrado = payments received; producción = performed plan lines by fecha de realización, as «Ingresos» (`null` without the revenue mirror). For the current month, `pace` carries the ritmo diario and, per figure, `week_need` («esta semana hay que generar») and `projection` (the end-of-month projection at the current pace: `gap` = projected − target). The ritmo diario is a catch-up pace: (meta − achieved before today) ÷ working days left, today included, each day weighed by its opening hours against the clinic's longest day (a 9–14 Saturday next to 9–19 weekdays weighs 0.5), times today's weight (`per_day`). Working days come from the clinic's opening hours and holidays, in the clinic's timezone. A figure with no target has a count and no pace. This is not the Ads goal, which is marketing's own.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": true,
            "name": "month",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Targets and progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "timezone": {
                          "type": "string"
                        },
                        "targets": {
                          "type": "object",
                          "properties": {
                            "month": {
                              "type": "string"
                            },
                            "primeras_visitas": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "updated_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "updated_by": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "month",
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp",
                            "updated_at",
                            "updated_by"
                          ]
                        },
                        "achieved": {
                          "type": "object",
                          "properties": {
                            "primeras_visitas": {
                              "type": "integer"
                            },
                            "cierres": {
                              "type": "integer"
                            },
                            "cobrado_clp": {
                              "type": "integer"
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "`null` without the revenue mirror."
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ]
                        },
                        "working_days": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "left": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "left"
                          ]
                        },
                        "history": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "date": {
                                "type": "string"
                              },
                              "booked": {
                                "type": "integer"
                              },
                              "primeras_visitas": {
                                "type": "integer"
                              },
                              "plata_clp": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "date",
                              "booked",
                              "primeras_visitas",
                              "plata_clp"
                            ]
                          }
                        },
                        "projections": {
                          "type": "object",
                          "properties": {
                            "primeras_visitas": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ],
                          "description": "Where the current month closes at the pace of the working days gone, with or without a target (the number a meta is picked from). `null` per figure outside the current month, while no working day has gone, for producción without the revenue mirror, and for cierres without a target."
                        },
                        "pace": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "primeras_visitas": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ]
                        }
                      },
                      "required": [
                        "month",
                        "timezone",
                        "targets",
                        "achieved",
                        "working_days",
                        "history",
                        "projections",
                        "pace"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "timezone": "America/Santiago",
                    "targets": {
                      "month": "2026-10",
                      "primeras_visitas": 40,
                      "cierres": 12,
                      "cobrado_clp": 18000000,
                      "produccion_clp": 60000000,
                      "updated_at": "2026-10-01T13:05:00.000Z",
                      "updated_by": null
                    },
                    "achieved": {
                      "primeras_visitas": 18,
                      "cierres": 5,
                      "cobrado_clp": 7450000,
                      "produccion_clp": 21300000
                    },
                    "working_days": {
                      "total": 21,
                      "left": 10
                    },
                    "history": [
                      {
                        "date": "2026-10-01",
                        "booked": 108,
                        "primeras_visitas": 26,
                        "plata_clp": 1251950
                      },
                      {
                        "date": "2026-10-02",
                        "booked": 49,
                        "primeras_visitas": 11,
                        "plata_clp": 359086
                      }
                    ],
                    "pace": {
                      "primeras_visitas": {
                        "status": "open",
                        "target": 40,
                        "achieved_before_today": 18,
                        "remaining": 22,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 3,
                        "week_need": 12,
                        "projection": {
                          "projected": 36,
                          "gap": -4,
                          "meets_target": false
                        }
                      },
                      "cierres": null,
                      "cobrado_clp": {
                        "status": "open",
                        "target": 18000000,
                        "achieved_before_today": 7450000,
                        "remaining": 10550000,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 1110527,
                        "week_need": 5550000,
                        "projection": {
                          "projected": 15900000,
                          "gap": -2100000,
                          "meets_target": false
                        }
                      },
                      "produccion_clp": {
                        "status": "open",
                        "target": 60000000,
                        "achieved_before_today": 21300000,
                        "remaining": 38700000,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 3927000,
                        "week_need": 19600000,
                        "projection": {
                          "projected": 52400000,
                          "gap": -7600000,
                          "meets_target": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the month's Meta mensual",
        "description": "Requires `clinic_admin:write`. Replaces the month's targets: a figure left out or `null` is cleared (except `produccion_clp`, which keeps its value when left out). Answers with the same targets and progress as the GET.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
            },
            "required": true,
            "name": "month",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "primeras_visitas": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "cierres": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "cobrado_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000000
                  },
                  "produccion_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "primeras_visitas": 40,
                "cierres": 12,
                "cobrado_clp": 18000000,
                "produccion_clp": 60000000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Targets and progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "month": {
                          "type": "string"
                        },
                        "timezone": {
                          "type": "string"
                        },
                        "targets": {
                          "type": "object",
                          "properties": {
                            "month": {
                              "type": "string"
                            },
                            "primeras_visitas": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "updated_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "updated_by": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "month",
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp",
                            "updated_at",
                            "updated_by"
                          ]
                        },
                        "achieved": {
                          "type": "object",
                          "properties": {
                            "primeras_visitas": {
                              "type": "integer"
                            },
                            "cierres": {
                              "type": "integer"
                            },
                            "cobrado_clp": {
                              "type": "integer"
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "`null` without the revenue mirror."
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ]
                        },
                        "working_days": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "left": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "left"
                          ]
                        },
                        "history": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "date": {
                                "type": "string"
                              },
                              "booked": {
                                "type": "integer"
                              },
                              "primeras_visitas": {
                                "type": "integer"
                              },
                              "plata_clp": {
                                "type": [
                                  "integer",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "date",
                              "booked",
                              "primeras_visitas",
                              "plata_clp"
                            ]
                          }
                        },
                        "projections": {
                          "type": "object",
                          "properties": {
                            "primeras_visitas": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ],
                          "description": "Where the current month closes at the pace of the working days gone, with or without a target (the number a meta is picked from). `null` per figure outside the current month, while no working day has gone, for producción without the revenue mirror, and for cierres without a target."
                        },
                        "pace": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "primeras_visitas": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "cierres": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            },
                            "produccion_clp": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                },
                                "week_need": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "description": "«Esta semana hay que generar»: the share of the remaining target the rest of this week carries (today through Sunday or the month end), weighed by opening hours. `null` unless `open`."
                                },
                                "projection": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "projected": {
                                      "type": "integer"
                                    },
                                    "gap": {
                                      "type": "integer"
                                    },
                                    "meets_target": {
                                      "type": "boolean"
                                    }
                                  },
                                  "required": [
                                    "projected",
                                    "gap",
                                    "meets_target"
                                  ],
                                  "description": "End-of-month projection at the current pace (what was achieved before today per weighted working day gone, carried over the weighted days left). `gap` = projected − target; `null` while no working day has gone."
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day",
                                "week_need",
                                "projection"
                              ]
                            }
                          },
                          "required": [
                            "primeras_visitas",
                            "cierres",
                            "cobrado_clp",
                            "produccion_clp"
                          ]
                        }
                      },
                      "required": [
                        "month",
                        "timezone",
                        "targets",
                        "achieved",
                        "working_days",
                        "history",
                        "projections",
                        "pace"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "month": "2026-10",
                    "timezone": "America/Santiago",
                    "targets": {
                      "month": "2026-10",
                      "primeras_visitas": 40,
                      "cierres": 12,
                      "cobrado_clp": 18000000,
                      "produccion_clp": 60000000,
                      "updated_at": "2026-10-01T13:05:00.000Z",
                      "updated_by": null
                    },
                    "achieved": {
                      "primeras_visitas": 18,
                      "cierres": 5,
                      "cobrado_clp": 7450000,
                      "produccion_clp": 21300000
                    },
                    "working_days": {
                      "total": 21,
                      "left": 10
                    },
                    "history": [
                      {
                        "date": "2026-10-01",
                        "booked": 108,
                        "primeras_visitas": 26,
                        "plata_clp": 1251950
                      },
                      {
                        "date": "2026-10-02",
                        "booked": 49,
                        "primeras_visitas": 11,
                        "plata_clp": 359086
                      }
                    ],
                    "pace": {
                      "primeras_visitas": {
                        "status": "open",
                        "target": 40,
                        "achieved_before_today": 18,
                        "remaining": 22,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 3,
                        "week_need": 12,
                        "projection": {
                          "projected": 36,
                          "gap": -4,
                          "meets_target": false
                        }
                      },
                      "cierres": null,
                      "cobrado_clp": {
                        "status": "open",
                        "target": 18000000,
                        "achieved_before_today": 7450000,
                        "remaining": 10550000,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 1110527,
                        "week_need": 5550000,
                        "projection": {
                          "projected": 15900000,
                          "gap": -2100000,
                          "meets_target": false
                        }
                      },
                      "produccion_clp": {
                        "status": "open",
                        "target": 60000000,
                        "achieved_before_today": 21300000,
                        "remaining": 38700000,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 3927000,
                        "week_need": 19600000,
                        "projection": {
                          "projected": 52400000,
                          "gap": -7600000,
                          "meets_target": false
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/today": {
      "get": {
        "summary": "The «Hoy» strip: is today on track?",
        "description": "Requires `clinic:read` or `clinic_money:read`; cobrado figures need `clinic_money:read` and are `null` without it. Bookings made today (citas created today, for any date; an anulación is not a booking; a moved cita stays a booking of the day it was made, and its replacement counts on the day it is re-booked) with their Tipo de agendamiento judged at the moment of booking, the bookings of the last hour, the same counts for the previous working day, cobrado today and the part from new patients (patients with a primera visita this month; `null` when the clinic's vendor has no payment mirror, e.g. Reservo), and the paces of today's month. The ritmo diario is a catch-up pace: (meta − achieved before today) ÷ working days left, today included, each day weighed by its opening hours against the clinic's longest day (a 9–14 Saturday next to 9–19 weekdays weighs 0.5), times today's weight (`per_day`). Working days come from the clinic's opening hours and holidays, in the clinic's timezone. A figure with no target has a count and no pace. The pace of cobrado is the Meta mensual's, set against the clinic's total cobrado.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "responses": {
          "200": {
            "description": "Today against the ritmo diario",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "as_of": {
                          "type": "string"
                        },
                        "timezone": {
                          "type": "string"
                        },
                        "today": {
                          "type": "object",
                          "properties": {
                            "date": {
                              "type": "string"
                            },
                            "booked": {
                              "type": "object",
                              "properties": {
                                "total": {
                                  "type": "integer"
                                },
                                "primeras_visitas": {
                                  "type": "integer"
                                },
                                "by_kind": {
                                  "type": "object",
                                  "properties": {
                                    "new": {
                                      "type": "integer"
                                    },
                                    "new_rebooked": {
                                      "type": "integer"
                                    },
                                    "in_treatment": {
                                      "type": "integer"
                                    }
                                  },
                                  "required": [
                                    "new",
                                    "new_rebooked",
                                    "in_treatment"
                                  ]
                                }
                              },
                              "required": [
                                "total",
                                "primeras_visitas",
                                "by_kind"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cobrado_new_patients_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "is_working_day": {
                              "type": "boolean"
                            },
                            "last_hour": {
                              "type": "object",
                              "properties": {
                                "total": {
                                  "type": "integer"
                                },
                                "primeras_visitas": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "total",
                                "primeras_visitas"
                              ]
                            },
                            "by_hour": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "hour": {
                                    "type": "integer",
                                    "minimum": 0,
                                    "maximum": 23
                                  },
                                  "total": {
                                    "type": "integer"
                                  },
                                  "primeras_visitas": {
                                    "type": "integer"
                                  },
                                  "by_kind": {
                                    "type": "object",
                                    "properties": {
                                      "new": {
                                        "type": "integer"
                                      },
                                      "new_rebooked": {
                                        "type": "integer"
                                      },
                                      "in_treatment": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "new",
                                      "new_rebooked",
                                      "in_treatment"
                                    ],
                                    "description": "The hour's citas by Tipo de agendamiento; a cita whose kind could not be judged counts in `total` only."
                                  }
                                },
                                "required": [
                                  "hour",
                                  "total",
                                  "primeras_visitas",
                                  "by_kind"
                                ]
                              }
                            },
                            "origin": {
                              "type": "object",
                              "properties": {
                                "meta": {
                                  "type": "integer"
                                },
                                "organic": {
                                  "type": "integer"
                                },
                                "unknown": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "meta",
                                "organic",
                                "unknown"
                              ]
                            },
                            "from_whatsapp": {
                              "type": "object",
                              "properties": {
                                "total": {
                                  "type": "integer"
                                },
                                "primeras_visitas": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "total",
                                "primeras_visitas"
                              ]
                            }
                          },
                          "required": [
                            "date",
                            "booked",
                            "cobrado_clp",
                            "cobrado_new_patients_clp",
                            "is_working_day",
                            "last_hour",
                            "by_hour",
                            "origin",
                            "from_whatsapp"
                          ]
                        },
                        "previous_working_day": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "date": {
                              "type": "string"
                            },
                            "booked": {
                              "type": "object",
                              "properties": {
                                "total": {
                                  "type": "integer"
                                },
                                "primeras_visitas": {
                                  "type": "integer"
                                },
                                "by_kind": {
                                  "type": "object",
                                  "properties": {
                                    "new": {
                                      "type": "integer"
                                    },
                                    "new_rebooked": {
                                      "type": "integer"
                                    },
                                    "in_treatment": {
                                      "type": "integer"
                                    }
                                  },
                                  "required": [
                                    "new",
                                    "new_rebooked",
                                    "in_treatment"
                                  ]
                                }
                              },
                              "required": [
                                "total",
                                "primeras_visitas",
                                "by_kind"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cobrado_new_patients_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "date",
                            "booked",
                            "cobrado_clp",
                            "cobrado_new_patients_clp"
                          ]
                        },
                        "pace": {
                          "type": "object",
                          "properties": {
                            "month": {
                              "type": "string"
                            },
                            "working_days_left": {
                              "type": "integer"
                            },
                            "primeras_visitas": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day"
                              ]
                            },
                            "cobrado_clp": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "status": {
                                  "type": "string",
                                  "enum": [
                                    "open",
                                    "met",
                                    "missed"
                                  ]
                                },
                                "target": {
                                  "type": "number"
                                },
                                "achieved_before_today": {
                                  "type": "number"
                                },
                                "remaining": {
                                  "type": "number"
                                },
                                "working_days_left": {
                                  "type": "integer"
                                },
                                "weighted_days_left": {
                                  "type": "number"
                                },
                                "day_weight": {
                                  "type": "number"
                                },
                                "per_day": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "status",
                                "target",
                                "achieved_before_today",
                                "remaining",
                                "working_days_left",
                                "weighted_days_left",
                                "day_weight",
                                "per_day"
                              ]
                            }
                          },
                          "required": [
                            "month",
                            "working_days_left",
                            "primeras_visitas",
                            "cobrado_clp"
                          ]
                        }
                      },
                      "required": [
                        "as_of",
                        "timezone",
                        "today",
                        "previous_working_day",
                        "pace"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "as_of": "2026-10-13T18:30:00.000Z",
                    "timezone": "America/Santiago",
                    "today": {
                      "date": "2026-10-13",
                      "is_working_day": true,
                      "booked": {
                        "total": 9,
                        "primeras_visitas": 4,
                        "by_kind": {
                          "new": 3,
                          "new_rebooked": 1,
                          "in_treatment": 5
                        }
                      },
                      "last_hour": {
                        "total": 2,
                        "primeras_visitas": 1
                      },
                      "by_hour": [
                        {
                          "hour": 9,
                          "total": 3,
                          "primeras_visitas": 1,
                          "by_kind": {
                            "new": 1,
                            "new_rebooked": 0,
                            "in_treatment": 2
                          }
                        },
                        {
                          "hour": 11,
                          "total": 4,
                          "primeras_visitas": 2,
                          "by_kind": {
                            "new": 1,
                            "new_rebooked": 1,
                            "in_treatment": 2
                          }
                        },
                        {
                          "hour": 14,
                          "total": 2,
                          "primeras_visitas": 1,
                          "by_kind": {
                            "new": 1,
                            "new_rebooked": 0,
                            "in_treatment": 1
                          }
                        }
                      ],
                      "origin": {
                        "meta": 2,
                        "organic": 1,
                        "unknown": 1
                      },
                      "from_whatsapp": {
                        "total": 3,
                        "primeras_visitas": 2
                      },
                      "cobrado_clp": 640000,
                      "cobrado_new_patients_clp": 90000
                    },
                    "previous_working_day": {
                      "date": "2026-10-09",
                      "booked": {
                        "total": 11,
                        "primeras_visitas": 3,
                        "by_kind": {
                          "new": 2,
                          "new_rebooked": 1,
                          "in_treatment": 8
                        }
                      },
                      "cobrado_clp": 810000,
                      "cobrado_new_patients_clp": 120000
                    },
                    "pace": {
                      "month": "2026-10",
                      "working_days_left": 14,
                      "primeras_visitas": {
                        "status": "open",
                        "target": 40,
                        "achieved_before_today": 18,
                        "remaining": 22,
                        "working_days_left": 10,
                        "weighted_days_left": 9.5,
                        "day_weight": 1,
                        "per_day": 3
                      },
                      "cobrado_clp": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/today/bookings": {
      "get": {
        "summary": "The citas behind the «Hoy» numbers",
        "description": "Requires `clinic:read`. The citas booked on one clinic day (`date`, default today; never a future day): when they were booked (the vendor's own creation time when the mirror carries it — Reservo — else when Vitrina first saw the cita, which is every Dentalink / Medilink cita), the hour of that in the clinic's clock, who the patient is, for when, with which professional, the Tipo de agendamiento judged as of the booking day, the Origen, whether the patient's linked contact had WhatsApp activity from two days before the booking day («Desde WhatsApp»), and the patient's last attended visit before that day. An anulación is not a booking; a moved cita («Cambio de fecha») stays a booking of the day it was made.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "date",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The day's bookings, in booking order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "date": {
                          "type": "string"
                        },
                        "timezone": {
                          "type": "string"
                        },
                        "rows": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "appointment_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "booked_at": {
                                "type": "string"
                              },
                              "hour": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 23
                              },
                              "starts_at": {
                                "type": "string"
                              },
                              "patient": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "name"
                                ]
                              },
                              "professional_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "kind": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "new",
                                  "new_rebooked",
                                  "in_treatment"
                                ]
                              },
                              "origin": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "kind": {
                                    "type": "string",
                                    "enum": [
                                      "meta",
                                      "organic",
                                      "unknown"
                                    ],
                                    "description": "`meta`: came from a Meta ad. `organic`: a linked contact with no ad touch. `unknown` («Sin dato»): no contact is linked to the person, so no ad touch can be read."
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Meta · Implantes octubre», «Meta» (ad name unknown), «Orgánico»."
                                  },
                                  "ad_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: the platform ad id (what `origin_ad_id` takes)."
                                  },
                                  "ad_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "ad_set_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "campaign_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "first_touch_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta only: when they first wrote through the ad (or came back)."
                                  },
                                  "via": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "first_message",
                                      "returned"
                                    ],
                                    "description": "`first_message`: the first thing they wrote came from the ad. `returned`: they were already writing and came back through one. Both count as people from ads."
                                  }
                                },
                                "required": [
                                  "kind",
                                  "label",
                                  "ad_id",
                                  "ad_name",
                                  "ad_set_name",
                                  "campaign_name",
                                  "first_touch_at",
                                  "via"
                                ],
                                "description": "Origen: the attribution Vitrina Ads reports (people from ads), asked of one person. A person with a linked contact and no ad touch is `organic`; one with no linked contact is `unknown`. Read-only."
                              },
                              "from_whatsapp": {
                                "type": "boolean"
                              },
                              "last_visit": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "appointment_id",
                              "booked_at",
                              "hour",
                              "starts_at",
                              "patient",
                              "professional_name",
                              "kind",
                              "origin",
                              "from_whatsapp",
                              "last_visit"
                            ]
                          }
                        }
                      },
                      "required": [
                        "date",
                        "timezone",
                        "rows"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "date": "2026-10-13",
                    "timezone": "America/Santiago",
                    "rows": [
                      {
                        "appointment_id": "6f1c2c1e-1d2b-4c9a-9f3e-0a1b2c3d4e5f",
                        "booked_at": "2026-10-13T14:12:00.000Z",
                        "hour": 11,
                        "starts_at": "2026-10-20T13:00:00.000Z",
                        "patient": {
                          "id": "0d9e8f7a-6b5c-4d3e-2f1a-0b9c8d7e6f5a",
                          "name": "Paciente Ejemplo"
                        },
                        "professional_name": "Dra. Ejemplo",
                        "kind": "new",
                        "origin": null,
                        "from_whatsapp": true,
                        "last_visit": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/state": {
      "get": {
        "summary": "The tenant's Vitrina Ads entitlement and delegated-key state",
        "description": "Distinguishes \"not entitled\" (`entitlement: off`) from \"needs re-mint\" (`needs_remint: true` — the live key is the wrong kind for the declared entitlement) and from an in-flight change (`pending_on` / `pending_off`). `grant_scopes` are the scopes the attribution engine returned on the last provision or entitlement call. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "Ads state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "entitlement": "on",
                    "needs_remint": false,
                    "key_kind": "ads",
                    "grant_scopes": [
                      "connections",
                      "attribution"
                    ],
                    "last_error": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/catalog": {
      "get": {
        "summary": "Conversion Sync catalog: definitions, connections, destinations, privacy settings",
        "description": "Step 0 of the setup. `definitions[].id` is a rule's `source_ref_id`; `connections[].id` is a destination's `connection_id`. Relayed verbatim from the conversion provider. Gated by the Add-on like every `/ads/*` route but `/ads/state`. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "Catalog",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsConversionSyncCatalog"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "definitions": [
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000001",
                        "conversion_key": "lead_created",
                        "source_event_names": [
                          "lead_created"
                        ]
                      },
                      {
                        "id": "f1f1f1f1-0000-4000-8000-000000000002",
                        "conversion_key": "closed_won",
                        "source_event_names": [
                          "closed_won"
                        ]
                      }
                    ],
                    "connections": [
                      {
                        "id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "provider": "meta_ads"
                      }
                    ],
                    "destinations": [
                      {
                        "id": "d0d0d0d0-0000-4000-8000-000000000001",
                        "destination": "meta_capi",
                        "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "meta_pixel_id": "1234567890123456",
                        "enabled": true
                      }
                    ],
                    "settings": {
                      "privacy_mode": "platform_safe",
                      "privacy_config": {
                        "parameter_allowlist": [
                          "value",
                          "currency"
                        ]
                      },
                      "export_meta_enabled": true,
                      "export_google_enabled": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/connections/{id}/pixels": {
      "get": {
        "summary": "The Meta pixels on a Meta Ads connection's ad account",
        "description": "Where `meta_pixel_id` comes from. One live Meta Graph call per request. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pixels",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdsMetaPixel"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "1234567890123456",
                      "name": "Píxel del sitio",
                      "created_at": "2025-11-03T15:20:00.000Z",
                      "last_fired_at": "2026-09-22T11:58:40.000Z",
                      "is_unavailable": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/destinations": {
      "get": {
        "summary": "The configured export destinations (pre-fills a re-run)",
        "description": "Relayed verbatim from the conversion provider, including each destination's `routing`. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "Destinations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsExportDestinations"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "privacy_mode": "platform_safe",
                    "export_meta_enabled": true,
                    "export_google_enabled": false,
                    "destinations": [
                      {
                        "id": "d0d0d0d0-0000-4000-8000-000000000001",
                        "destination": "meta_capi",
                        "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "meta_pixel_id": "1234567890123456",
                        "enabled": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create or update the Meta CAPI destination",
        "description": "Creates the `meta_capi` destination (or updates the existing one on a re-run). For a clinic (`vertical = healthcare`) the profile is first switched to `privacy_mode = platform_safe` and the write is refused unless the read-back confirms it. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "connection_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The Meta Ads connection (`catalog.connections[].id`)."
                  },
                  "meta_pixel_id": {
                    "type": "string",
                    "pattern": "^\\d{5,32}$",
                    "description": "From `GET /ads/conversion-sync/connections/{id}/pixels`."
                  }
                },
                "required": [
                  "connection_id",
                  "meta_pixel_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                "meta_pixel_id": "1234567890123456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The destination and the privacy check",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsDestinationResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "action": "created",
                    "destination": {
                      "id": "d0d0d0d0-0000-4000-8000-000000000001",
                      "destination": "meta_capi",
                      "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                      "meta_pixel_id": "1234567890123456",
                      "enabled": true
                    },
                    "privacy": {
                      "required": "platform_safe",
                      "actual": "platform_safe",
                      "applied": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/rules/sync": {
      "post": {
        "summary": "Create or update one export rule per outcome Vitrina emits",
        "description": "Automotive: `lead_created`→Lead, `appointment_booked`→Schedule, `payment_received`→Purchase (event value). Clinic: `lead_created`→Lead, `appointment_booked`→Schedule, `appointment_attended` and `quote_presented`→generic custom events without value, `payment_received` with its Meta destination DISABLED (instalments never reach Meta), and the Purchase on the stage the clinic chose: the accepted presupuesto (`closed_won`, with its accepted value; the default) or — after the owner’s consented switch in «Enviar a Meta» (`send_stage:first_payment:on`) — the first payment of each accepted plan (`first_payment`, once per plan), `closed_won` then being a custom event without value. A clinic that keeps NO plans (no presupuesto, no mirrored plan — its data decides, never a setting) has the same `first_payment` rule carry each PERSON’s first payment instead: the first payment > 0 of a person, once ever, at the amount paid. This route NEVER moves the Purchase, and a stage turned off from «Enviar a Meta» stays off. Idempotent: matched to existing rules by definition; a rule that already holds its body is `unchanged` (nothing written). A definition missing from the catalog is reported as `skipped`. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "What each rule did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsRuleSyncResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "vertical": "healthcare",
                    "rules": [
                      {
                        "stage": "lead_created",
                        "action": "created",
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "meta_enabled": true,
                        "meta_event_name": "Lead"
                      },
                      {
                        "stage": "appointment_booked",
                        "action": "created",
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000002",
                        "meta_enabled": true,
                        "meta_event_name": "Schedule"
                      },
                      {
                        "stage": "closed_won",
                        "action": "created",
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000003",
                        "meta_enabled": true,
                        "meta_event_name": "Purchase"
                      },
                      {
                        "stage": "payment_received",
                        "action": "created",
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000004",
                        "meta_enabled": false,
                        "meta_event_name": "Purchase"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/setup": {
      "post": {
        "summary": "One action: privacy mode (clinics) → destination → rules",
        "description": "The setup in one call — the same work as the destination write followed by the rules sync. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "connection_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The Meta Ads connection (`catalog.connections[].id`)."
                  },
                  "meta_pixel_id": {
                    "type": "string",
                    "pattern": "^\\d{5,32}$",
                    "description": "From `GET /ads/conversion-sync/connections/{id}/pixels`."
                  }
                },
                "required": [
                  "connection_id",
                  "meta_pixel_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                "meta_pixel_id": "1234567890123456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Destination and rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsConversionSyncSetupResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "destination": {
                      "action": "created",
                      "destination": {
                        "id": "d0d0d0d0-0000-4000-8000-000000000001",
                        "destination": "meta_capi",
                        "connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                        "meta_pixel_id": "1234567890123456",
                        "enabled": true
                      },
                      "privacy": {
                        "required": "platform_safe",
                        "actual": "platform_safe",
                        "applied": true
                      }
                    },
                    "rules": {
                      "vertical": "healthcare",
                      "rules": [
                        {
                          "stage": "lead_created",
                          "action": "created",
                          "rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
                          "meta_enabled": true,
                          "meta_event_name": "Lead"
                        },
                        {
                          "stage": "appointment_booked",
                          "action": "created",
                          "rule_id": "e1e1e1e1-0000-4000-8000-000000000002",
                          "meta_enabled": true,
                          "meta_event_name": "Schedule"
                        },
                        {
                          "stage": "closed_won",
                          "action": "created",
                          "rule_id": "e1e1e1e1-0000-4000-8000-000000000003",
                          "meta_enabled": true,
                          "meta_event_name": "Purchase"
                        },
                        {
                          "stage": "payment_received",
                          "action": "created",
                          "rule_id": "e1e1e1e1-0000-4000-8000-000000000004",
                          "meta_enabled": false,
                          "meta_event_name": "Purchase"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/wiring": {
      "get": {
        "summary": "Whether the ad sets optimize for the events Vitrina sends",
        "description": "The conversion provider's per-rule verdicts and remediation, verbatim (served from a 6-hour cache; `force=1` re-verifies live — still a read). `privacy` reports whether a clinic profile carries `platform_safe`. `sample=1` (or a sandbox workspace) answers the sample dataset's verdicts — one `wired` row per rule the vertical sends to Meta, fresh, no entitlement required and nothing read — like every other Ads read. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ],
              "description": "Re-verify live against Meta instead of the conversion provider's 6-hour cache. Still a read — several Meta API calls, use sparingly."
            },
            "required": false,
            "description": "Re-verify live against Meta instead of the conversion provider's 6-hour cache. Still a read — several Meta API calls, use sparingly.",
            "name": "force",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Wiring verdicts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsConversionSyncWiring"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "results": [
                      {
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "destination_id": "d0d0d0d0-0000-4000-8000-000000000001",
                        "verdict": "wired",
                        "event_name": "Purchase",
                        "checks": {
                          "connection": {
                            "ok": true
                          },
                          "pixel": {
                            "ok": true,
                            "isUnavailable": false,
                            "lastFiredAt": "2026-09-22T11:53:00.000Z"
                          },
                          "eventsArriving": {
                            "ok": true,
                            "volume7d": 64
                          },
                          "customConversion": {
                            "exists": false,
                            "required": false
                          },
                          "optimizationGoal": {
                            "usedByActiveAdSets": [
                              {
                                "adSetName": "Implantes · Las Condes",
                                "campaignName": "Implantes · Septiembre",
                                "objective": "OUTCOME_SALES",
                                "optimizationGoal": "OFFSITE_CONVERSIONS",
                                "customEventType": "PURCHASE",
                                "via": "custom_event_type"
                              }
                            ]
                          }
                        },
                        "remediation": [],
                        "checked_at": "2026-09-22T12:00:00.000Z",
                        "stale": false
                      }
                    ],
                    "checked_at": "2026-09-22T12:00:00.000Z",
                    "stale": false,
                    "ttl_ms": 21600000,
                    "privacy": {
                      "required": "platform_safe",
                      "actual": "platform_safe",
                      "applied": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/wiring/recheck": {
      "post": {
        "summary": "Re-verify wiring and self-heal (creates a missing Custom Conversion)",
        "description": "A WRITE: the self-heal creates a Custom Conversion in the dealer's Meta ad account. Bind it to an explicit operator action only — never a page load or a poll. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rule_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Limit the recheck to one rule. Omit to recheck every rule."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "rule_id": "e1e1e1e1-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Fresh wiring verdicts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsConversionSyncWiring"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "results": [
                      {
                        "rule_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "destination_id": "d0d0d0d0-0000-4000-8000-000000000001",
                        "verdict": "wired",
                        "event_name": "Purchase",
                        "checks": {
                          "connection": {
                            "ok": true
                          },
                          "pixel": {
                            "ok": true,
                            "isUnavailable": false,
                            "lastFiredAt": "2026-09-22T11:53:00.000Z"
                          },
                          "eventsArriving": {
                            "ok": true,
                            "volume7d": 64
                          },
                          "customConversion": {
                            "exists": false,
                            "required": false
                          },
                          "optimizationGoal": {
                            "usedByActiveAdSets": [
                              {
                                "adSetName": "Implantes · Las Condes",
                                "campaignName": "Implantes · Septiembre",
                                "objective": "OUTCOME_SALES",
                                "optimizationGoal": "OFFSITE_CONVERSIONS",
                                "customEventType": "PURCHASE",
                                "via": "custom_event_type"
                              }
                            ]
                          }
                        },
                        "remediation": [],
                        "checked_at": "2026-09-22T12:00:00.000Z",
                        "stale": false
                      }
                    ],
                    "checked_at": "2026-09-22T12:00:00.000Z",
                    "stale": false,
                    "ttl_ms": 21600000,
                    "privacy": {
                      "required": "platform_safe",
                      "actual": "platform_safe",
                      "applied": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/status": {
      "get": {
        "summary": "What reaches Meta, stage by stage («Enviar a Meta»)",
        "description": "One read for the «Enviar a Meta» page and the wizard step: the state (`not_started | on | partial | off | blocked`, `wizard_done` = the step is resolved), the datasets of the connected ad account with the recommended one (the dataset active ad sets already optimise on, else the most recent), one lane per stage (the Meta event, its value, on/off, a typed wiring verdict, active ad sets, deliveries over 7 days with a per-day series), the Meta grant, the health-data protection, the owner’s consent and Meta links built on the server. The Meta connection is resolved server-side. A stage the workspace cannot send yet because its definition is missing heals on its own. `sample=1` (or a sandbox) answers the healthy sample page with a scripted arrival per lane (`sample_arrival_ms`). Poll-safe (`Cache-Control: no-store`). Changes go through `GET /ads/actions?screen=envio` (`send_setup`, `send_stage`, `send_dataset`, `send_stop`). Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The send status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsSendStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "partial",
                    "blocked_reason": null,
                    "wizard_done": true,
                    "meta_connection": "connected",
                    "dataset": {
                      "external_id": "1234567890123456",
                      "id_tail": "3456",
                      "name": "Clínica Dental",
                      "last_fired_at": "2026-09-25T11:40:00.000Z",
                      "is_unavailable": false
                    },
                    "datasets": [
                      {
                        "external_id": "1234567890123456",
                        "id_tail": "3456",
                        "name": "Clínica Dental",
                        "last_fired_at": "2026-09-25T11:40:00.000Z",
                        "is_unavailable": false,
                        "recommended": true,
                        "recommended_reason": "used_by_ad_sets",
                        "selected": true
                      }
                    ],
                    "whatsapp_dataset": null,
                    "stages": [
                      {
                        "stage": "lead_created",
                        "label": "Contacto nuevo",
                        "meta_event": "Lead",
                        "meta_event_names": [
                          "Lead"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "standard",
                        "meta_event_neutral": false,
                        "value": "none",
                        "enabled": true,
                        "switchable": true,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": true,
                        "ready_reason": null,
                        "count_28d": 212,
                        "weekly_28d": 49.5,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 212,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 212,
                        "verdict": "below_threshold",
                        "active_ad_sets": 0,
                        "last_sent_at": "2026-09-25T12:40:00.000Z",
                        "sent_7d": 12,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "appointment_booked",
                        "label": "Cita agendada",
                        "meta_event": "Schedule",
                        "meta_event_names": [
                          "Schedule"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "standard",
                        "meta_event_neutral": false,
                        "value": "none",
                        "enabled": true,
                        "switchable": true,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": true,
                        "ready_reason": null,
                        "count_28d": 96,
                        "weekly_28d": 22.4,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 96,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 96,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": "2026-09-25T11:40:00.000Z",
                        "sent_7d": 11,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "appointment_attended",
                        "label": "Cita atendida",
                        "meta_event": "vitrina_step_3",
                        "meta_event_names": [
                          "vitrina_step_3"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "custom",
                        "meta_event_neutral": true,
                        "value": "none",
                        "enabled": false,
                        "switchable": true,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": false,
                        "ready_reason": "off",
                        "count_28d": 71,
                        "weekly_28d": 16.6,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 71,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 71,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": "2026-09-25T10:40:00.000Z",
                        "sent_7d": 10,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": "2026-09-23T13:40:00.000Z",
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "quote_presented",
                        "label": "Presupuesto presentado",
                        "meta_event": "vitrina_step_4",
                        "meta_event_names": [
                          "vitrina_step_4"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "custom",
                        "meta_event_neutral": true,
                        "value": "none",
                        "enabled": true,
                        "switchable": true,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": true,
                        "ready_reason": null,
                        "count_28d": 40,
                        "weekly_28d": 9.3,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 40,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 40,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": "2026-09-25T09:40:00.000Z",
                        "sent_7d": 9,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "closed_won",
                        "label": "Presupuesto aceptado",
                        "meta_event": "vitrina_step_5",
                        "meta_event_names": [
                          "vitrina_step_5"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "custom",
                        "meta_event_neutral": true,
                        "value": "none",
                        "enabled": true,
                        "switchable": true,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": true,
                        "ready_reason": null,
                        "count_28d": 22,
                        "weekly_28d": 5.1,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 22,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 22,
                        "verdict": "wired",
                        "active_ad_sets": 2,
                        "last_sent_at": "2026-09-25T08:40:00.000Z",
                        "sent_7d": 8,
                        "meta_volume_7d": 14,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 1
                          },
                          {
                            "date": "2026-09-24",
                            "count": 1
                          },
                          {
                            "date": "2026-09-25",
                            "count": 1
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "first_payment",
                        "label": "Primer pago de un tratamiento nuevo",
                        "meta_event": "Purchase",
                        "meta_event_names": [
                          "Purchase"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "standard",
                        "meta_event_neutral": false,
                        "value": "accepted_total",
                        "enabled": false,
                        "switchable": false,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "definition_missing",
                        "ready": false,
                        "ready_reason": "definition_missing",
                        "count_28d": 64,
                        "weekly_28d": 14.9,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 0,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 0,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": null,
                        "sent_7d": 0,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "returning_first_payment",
                        "label": "Primer pago de un tratamiento de un paciente que vuelve",
                        "meta_event": "vitrina_step_7",
                        "meta_event_names": [
                          "vitrina_step_7"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "custom",
                        "meta_event_neutral": true,
                        "value": "none",
                        "enabled": false,
                        "switchable": false,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "definition_missing",
                        "ready": false,
                        "ready_reason": "definition_missing",
                        "count_28d": 0,
                        "weekly_28d": 0,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 0,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 0,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": null,
                        "sent_7d": 0,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "payment_received",
                        "label": "Pago recibido",
                        "meta_event": "Purchase",
                        "meta_event_names": [
                          "Purchase"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "standard",
                        "meta_event_neutral": false,
                        "value": "payment",
                        "enabled": false,
                        "switchable": false,
                        "lock": "installments",
                        "awaiting_switch": false,
                        "source": "available",
                        "ready": false,
                        "ready_reason": "installments",
                        "count_28d": 58,
                        "weekly_28d": 13.5,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 58,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 58,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": "2026-09-25T07:40:00.000Z",
                        "sent_7d": 7,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "checkout_started",
                        "label": "Abrió el link de pago",
                        "meta_event": "InitiateCheckout",
                        "meta_event_names": [
                          "InitiateCheckout"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "standard",
                        "meta_event_neutral": false,
                        "value": "payment",
                        "enabled": false,
                        "switchable": false,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "definition_missing",
                        "ready": false,
                        "ready_reason": "definition_missing",
                        "count_28d": 0,
                        "weekly_28d": 0,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 0,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 0,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": null,
                        "sent_7d": 0,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      },
                      {
                        "stage": "deposit_paid",
                        "label": "Abono pagado",
                        "meta_event": "vitrina_step_8",
                        "meta_event_names": [
                          "vitrina_step_8"
                        ],
                        "meta_event_messaging": null,
                        "meta_event_kind": "custom",
                        "meta_event_neutral": true,
                        "value": "payment",
                        "enabled": false,
                        "switchable": false,
                        "lock": null,
                        "awaiting_switch": false,
                        "source": "definition_missing",
                        "ready": false,
                        "ready_reason": "definition_missing",
                        "count_28d": 0,
                        "weekly_28d": 0,
                        "measured_weeks": 4,
                        "measured_days": 30,
                        "window_days": 30,
                        "events_7d": 0,
                        "with_ad_click_7d": 0,
                        "events_30d": 0,
                        "with_ad_click_30d": 0,
                        "without_ad_click_7d": 0,
                        "without_ad_click_30d": 0,
                        "verdict": "not_checked",
                        "active_ad_sets": 0,
                        "last_sent_at": null,
                        "sent_7d": 0,
                        "meta_volume_7d": null,
                        "series_7d": [
                          {
                            "date": "2026-09-19",
                            "count": 0
                          },
                          {
                            "date": "2026-09-20",
                            "count": 0
                          },
                          {
                            "date": "2026-09-21",
                            "count": 0
                          },
                          {
                            "date": "2026-09-22",
                            "count": 0
                          },
                          {
                            "date": "2026-09-23",
                            "count": 0
                          },
                          {
                            "date": "2026-09-24",
                            "count": 0
                          },
                          {
                            "date": "2026-09-25",
                            "count": 0
                          }
                        ],
                        "weekly_learning_target": 50,
                        "changed_by": null,
                        "changed_at": null,
                        "value_capped_7d": 0,
                        "value_cap_clp": null,
                        "too_old_28d": 0,
                        "purchases_reversed_28d": 0
                      }
                    ],
                    "enabled_count": 4,
                    "waiting_count": 0,
                    "stage_count": 9,
                    "first_payment_candidates": {
                      "basis": "plan",
                      "count_28d": 64,
                      "weekly_28d": 14.9,
                      "measured_weeks": 4,
                      "measured_days": 30,
                      "mirror_count_28d": null,
                      "mirror_weekly_28d": null
                    },
                    "first_payment_click_match": {
                      "stage": "first_payment",
                      "required_7d": 50,
                      "with_ad_click_7d": 0,
                      "remaining_7d": 50,
                      "reached": false,
                      "without_ad_click_7d": 0,
                      "with_ad_click_30d": 0,
                      "without_ad_click_30d": 0,
                      "held_back_30d": null
                    },
                    "restriction": {
                      "kind": "unknown",
                      "since": null
                    },
                    "protected_dataset": null,
                    "recommended_optimization_stage": {
                      "stage": "appointment_attended",
                      "reason": "rate_band",
                      "rate": 0.33490566037735847
                    },
                    "purchase_stage": "first_payment",
                    "purchase_switch_available": false,
                    "grant": "full",
                    "protection_required": true,
                    "protected": true,
                    "consent": {
                      "version": "2026-10-08.send-to-meta.v3",
                      "current": true,
                      "accepted_at": "2026-09-22T13:40:00.000Z"
                    },
                    "meta_links": {
                      "create_dataset": "https://business.facebook.com/events_manager2/overview?act=1000000000000001",
                      "events_manager": "https://business.facebook.com/events_manager2/list/pixel/1234567890123456/overview?act=1000000000000001",
                      "test_events": "https://business.facebook.com/events_manager2/list/pixel/1234567890123456/test_events?act=1000000000000001",
                      "ad_sets": "https://www.facebook.com/adsmanager/manage/adsets?act=1000000000000001"
                    },
                    "checked_at": "2026-09-25T10:40:00.000Z",
                    "stale": false,
                    "sample": false,
                    "ad_set_goals": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/preview-event": {
      "get": {
        "summary": "A real event, field by field, as Meta receives it",
        "description": "Built from the workspace’s own last event of the stage (else an example, `source: example`). Each field says how it reaches Meta — `sent`, `hashed` (matchable, not readable), `withheld` or `absent` — and identifiers never carry a value or a hash. `never_sent` lists what never leaves Vitrina. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "lead_created",
                "lead_qualified",
                "appointment_booked",
                "checkout_started",
                "deposit_paid",
                "appointment_attended",
                "quote_presented",
                "closed_won",
                "first_payment",
                "returning_first_payment",
                "payment_received",
                "seller_qualified",
                "seller_visit_booked",
                "seller_deal_closed"
              ],
              "description": "The outcome stage to preview. One the workspace sends (a stage outside its vertical → 404)."
            },
            "required": true,
            "description": "The outcome stage to preview. One the workspace sends (a stage outside its vertical → 404).",
            "name": "stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The previewed event",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsSendPreviewEvent"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "stage": "first_payment",
                    "source": "own",
                    "vitrina": {
                      "stage_label": "Primer pago de un tratamiento nuevo",
                      "occurred_at": "2026-09-25T08:40:00.000Z",
                      "value_clp": 1200000,
                      "contact_tail": "a3f2"
                    },
                    "meta": {
                      "event": "Purchase",
                      "value_clp": 1038075,
                      "capped": true,
                      "value_cap_clp": 1038075,
                      "reaches_meta": true,
                      "not_sent_reason": null,
                      "fields": [
                        {
                          "key": "event",
                          "label": "Evento",
                          "state": "sent",
                          "display": "Purchase"
                        },
                        {
                          "key": "time",
                          "label": "Cuándo",
                          "state": "sent",
                          "display": "2026-09-25T08:40:00.000Z"
                        },
                        {
                          "key": "value",
                          "label": "Valor",
                          "state": "sent",
                          "display": "1.038.075 CLP"
                        },
                        {
                          "key": "action_source",
                          "label": "Dónde pasó",
                          "state": "sent",
                          "display": "business_messaging",
                          "display_channel": "whatsapp"
                        },
                        {
                          "key": "email",
                          "label": "Correo",
                          "state": "hashed"
                        },
                        {
                          "key": "phone",
                          "label": "Teléfono",
                          "state": "hashed"
                        },
                        {
                          "key": "contact_ref",
                          "label": "Id del contacto",
                          "state": "hashed"
                        },
                        {
                          "key": "ad_click",
                          "label": "Clic del anuncio",
                          "state": "sent"
                        }
                      ]
                    },
                    "never_sent": [
                      "national_id",
                      "clinical_data",
                      "notes",
                      "treatment_name",
                      "site_urls"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/test": {
      "post": {
        "summary": "Send one Meta TEST event (Test Events only)",
        "description": "One synthetic event to the dataset Meta receives the workspace’s events in, always with a Test Events code (generated when omitted), so it shows in Events Manager → Test Events and never counts as a real event. `result`: `received`, `rejected` (Meta refused it) or `blocked` (the privacy protection held it back). `409 CONFLICT` (`details.reason: not_set_up`) before the send is set up; `429` when Meta limits test events (5 a minute). `sample` simulates it. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stage": {
                    "type": "string",
                    "enum": [
                      "lead_created",
                      "lead_qualified",
                      "appointment_booked",
                      "checkout_started",
                      "deposit_paid",
                      "appointment_attended",
                      "quote_presented",
                      "closed_won",
                      "first_payment",
                      "returning_first_payment",
                      "payment_received",
                      "seller_qualified",
                      "seller_visit_booked",
                      "seller_deal_closed"
                    ],
                    "description": "Which stage the test imitates (its rule and event name). Defaults to the Purchase stage, else the first stage that reaches Meta."
                  },
                  "test_event_code": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_-]{4,64}$",
                    "description": "The code Meta shows in Events Manager → Test Events. Omit it and one is generated: a test ALWAYS carries a code, so it lands in Test Events only and never counts as a real event."
                  },
                  "channel": {
                    "type": "string",
                    "enum": [
                      "website",
                      "business_messaging"
                    ],
                    "description": "`business_messaging` imitates an event born in a WhatsApp conversation. Default `website`."
                  },
                  "sample": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "stage": "closed_won"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What Meta answered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsSendTestResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "result": "received",
                    "test_event_code": "TEST04821",
                    "event_id": "a0a0a0a0-0000-4000-8000-000000000001",
                    "stage": "closed_won",
                    "meta_event": "Purchase",
                    "quality_warning": false,
                    "test_events_link": "https://business.facebook.com/events_manager2/list/pixel/1234567890123456/test_events?act=1000000000000001",
                    "simulated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/messaging-event-names": {
      "get": {
        "summary": "Each stage’s Meta event name on the website and WhatsApp lanes",
        "description": "Per stage the workspace sends: the website pixel lane’s name (`website_event`, fixed), the WhatsApp (business_messaging) lane’s default (`messaging_default`) and the name in force (`messaging_event`: the workspace’s choice, else the default; `null` = not sent on that lane). `accepted_events` are the 14 names Meta’s WhatsApp dataset accepts. Change them with `PATCH` on this path. `sample=1` (or a sandbox) answers the defaults. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The stages and their names",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMessagingEventNames"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "overrides": {
                      "lead_qualified": "InitiateCheckout"
                    },
                    "accepted_events": [
                      "Purchase",
                      "LeadSubmitted",
                      "InitiateCheckout",
                      "AddToCart",
                      "ViewContent",
                      "OrderCreated",
                      "OrderShipped",
                      "OrderDelivered",
                      "OrderCanceled",
                      "OrderReturned",
                      "CartAbandoned",
                      "QualifiedLead",
                      "RatingProvided",
                      "ReviewProvided"
                    ],
                    "stages": [
                      {
                        "stage": "lead_created",
                        "label": "Contacto nuevo",
                        "website_event": "Lead",
                        "messaging_default": "LeadSubmitted",
                        "messaging_event": "LeadSubmitted",
                        "overridden": false
                      },
                      {
                        "stage": "lead_qualified",
                        "label": "Cotizó",
                        "website_event": "QualifiedLead",
                        "messaging_default": "QualifiedLead",
                        "messaging_event": "InitiateCheckout",
                        "overridden": true
                      },
                      {
                        "stage": "appointment_booked",
                        "label": "Visita agendada",
                        "website_event": "Schedule",
                        "messaging_default": "QualifiedLead",
                        "messaging_event": "QualifiedLead",
                        "overridden": false
                      },
                      {
                        "stage": "checkout_started",
                        "label": "Abrió el link de pago",
                        "website_event": "InitiateCheckout",
                        "messaging_default": "InitiateCheckout",
                        "messaging_event": "InitiateCheckout",
                        "overridden": false
                      },
                      {
                        "stage": "deposit_paid",
                        "label": "Abono pagado",
                        "website_event": "vitrina_step_8",
                        "messaging_default": "OrderCreated",
                        "messaging_event": "OrderCreated",
                        "overridden": false
                      },
                      {
                        "stage": "closed_won",
                        "label": "Venta o compra",
                        "website_event": "Purchase",
                        "messaging_default": "Purchase",
                        "messaging_event": "Purchase",
                        "overridden": false
                      },
                      {
                        "stage": "payment_received",
                        "label": "Pago recibido",
                        "website_event": "Purchase",
                        "messaging_default": "Purchase",
                        "messaging_event": "Purchase",
                        "overridden": false
                      },
                      {
                        "stage": "seller_qualified",
                        "label": "Vendedor calificado",
                        "website_event": "SellerQualified",
                        "messaging_default": "AddToCart",
                        "messaging_event": "AddToCart",
                        "overridden": false
                      },
                      {
                        "stage": "seller_visit_booked",
                        "label": "Vendedor con visita agendada",
                        "website_event": "SellerVisitScheduled",
                        "messaging_default": null,
                        "messaging_event": null,
                        "overridden": false
                      },
                      {
                        "stage": "seller_deal_closed",
                        "label": "Compra cerrada a un vendedor",
                        "website_event": "SellerDealClosed",
                        "messaging_default": "OrderDelivered",
                        "messaging_event": "OrderDelivered",
                        "overridden": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Choose a stage’s Meta event name on the WhatsApp lane",
        "description": "Merges onto the stored choices: `overrides` sets a stage’s name (one of `accepted_events`, or `null` = do not send it on the WhatsApp lane), `reset` drops a choice (back to `messaging_default`). The website lane never changes. The RESULT is validated, and nothing is stored on a `422` (`field_errors.overrides.<stage>`, `details.problems.<stage>`): `unknown_stage` (the workspace does not send it), `stage_not_sent` (instalments), `name_not_accepted`, `purchase_needs_value` (Purchase only on a stage that carries a value), `seller_name_shared` (a seller-side stage never shares its name with a buyer stage, so an ad set that optimises on buyers never learns from sellers; buyer stages may share). Then the engine’s live rules are converged at once (`sync.state: converged`, `sync.updated` = the stages rewritten); `pending` = stored, the daily sweep retries the write. Requires `ads:write`. Errors: `402 ENTITLEMENT_NOT_ACTIVE` (`details.feature: vitrina_ads`, with `hint`) when the Vitrina Ads Add-on is not active — never retried; `409 ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating, or still lacks `exports:write` after one automatic re-mint (the entitlement rail converges it; see `GET /ads/state`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsMessagingEventNamesPatch"
              },
              "example": {
                "overrides": {
                  "lead_qualified": "InitiateCheckout"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored choices and the engine write",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMessagingEventNamesPatchResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "overrides": {
                      "lead_qualified": "InitiateCheckout"
                    },
                    "accepted_events": [
                      "Purchase",
                      "LeadSubmitted",
                      "InitiateCheckout",
                      "AddToCart",
                      "ViewContent",
                      "OrderCreated",
                      "OrderShipped",
                      "OrderDelivered",
                      "OrderCanceled",
                      "OrderReturned",
                      "CartAbandoned",
                      "QualifiedLead",
                      "RatingProvided",
                      "ReviewProvided"
                    ],
                    "stages": [
                      {
                        "stage": "lead_created",
                        "label": "Contacto nuevo",
                        "website_event": "Lead",
                        "messaging_default": "LeadSubmitted",
                        "messaging_event": "LeadSubmitted",
                        "overridden": false
                      },
                      {
                        "stage": "lead_qualified",
                        "label": "Cotizó",
                        "website_event": "QualifiedLead",
                        "messaging_default": "QualifiedLead",
                        "messaging_event": "InitiateCheckout",
                        "overridden": true
                      },
                      {
                        "stage": "appointment_booked",
                        "label": "Visita agendada",
                        "website_event": "Schedule",
                        "messaging_default": "QualifiedLead",
                        "messaging_event": "QualifiedLead",
                        "overridden": false
                      },
                      {
                        "stage": "checkout_started",
                        "label": "Abrió el link de pago",
                        "website_event": "InitiateCheckout",
                        "messaging_default": "InitiateCheckout",
                        "messaging_event": "InitiateCheckout",
                        "overridden": false
                      },
                      {
                        "stage": "deposit_paid",
                        "label": "Abono pagado",
                        "website_event": "vitrina_step_8",
                        "messaging_default": "OrderCreated",
                        "messaging_event": "OrderCreated",
                        "overridden": false
                      },
                      {
                        "stage": "closed_won",
                        "label": "Venta o compra",
                        "website_event": "Purchase",
                        "messaging_default": "Purchase",
                        "messaging_event": "Purchase",
                        "overridden": false
                      },
                      {
                        "stage": "payment_received",
                        "label": "Pago recibido",
                        "website_event": "Purchase",
                        "messaging_default": "Purchase",
                        "messaging_event": "Purchase",
                        "overridden": false
                      },
                      {
                        "stage": "seller_qualified",
                        "label": "Vendedor calificado",
                        "website_event": "SellerQualified",
                        "messaging_default": "AddToCart",
                        "messaging_event": "AddToCart",
                        "overridden": false
                      },
                      {
                        "stage": "seller_visit_booked",
                        "label": "Vendedor con visita agendada",
                        "website_event": "SellerVisitScheduled",
                        "messaging_default": null,
                        "messaging_event": null,
                        "overridden": false
                      },
                      {
                        "stage": "seller_deal_closed",
                        "label": "Compra cerrada a un vendedor",
                        "website_event": "SellerDealClosed",
                        "messaging_default": "OrderDelivered",
                        "messaging_event": "OrderDelivered",
                        "overridden": false
                      }
                    ],
                    "sync": {
                      "state": "converged",
                      "updated": [
                        "lead_qualified"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/data-sources": {
      "get": {
        "summary": "The Meta datasets (WhatsApp, pixel, Instagram): state, prerequisites and a 30-day preview",
        "description": "The Meta destinations of the workspace’s outcomes, each set up by its own explicit press, never as a side effect. Step 1 — the dataset linked to the WhatsApp Business Account — receives the outcomes carrying the click-to-WhatsApp ad click (`ctwa_clid`), as `business_messaging`; a click-to-WhatsApp ad set optimised on Purchase needs it. Step 2 — the website pixel — receives every other outcome, matched by the hashed phone and e-mail; without it those outcomes (most payments) are not sent at all. Step 3 — the dataset linked to the Instagram account — receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id (measurement only: Meta offers no Purchase optimisation on Instagram); until it exists they reach the website pixel as before. Each sends from its press on (no history), and nothing flows until the stages are switched on in «Enviar a Meta». Meta cannot delete a dataset. Every state is the measurement engine’s read-back: `sending` only when it confirms an enabled destination AND an enabled Meta rule. `coming_soon` until the engine can create it. Each step carries the notice to show before its press; the pixel step lists the ad account’s pixels to choose. Requires `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The dataset state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "prerequisites": {
                      "meta_ads_connected": true,
                      "whatsapp_connected": true,
                      "instagram_connected": true
                    },
                    "whatsapp": {
                      "kind": "whatsapp",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-whatsapp-dataset-2026-10-02",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "website": {
                      "kind": "website",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-website-pixel-2026-10-02",
                        "points": [
                          "Tu píxel del sitio recibe los resultados que no traen el clic…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      },
                      "candidates": [
                        {
                          "external_id": "<pixel_id>",
                          "id_tail": "9748",
                          "name": "Píxel del sitio",
                          "last_fired_at": "2026-10-01T22:10:00.000Z",
                          "recommended": true
                        }
                      ],
                      "can_create_pixel": false
                    },
                    "instagram": {
                      "kind": "instagram",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-instagram-dataset-2026-10-07",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "click_less_not_sent": false,
                    "preview": {
                      "window_days": 30,
                      "total": 120,
                      "with_ad_click": 14,
                      "without_ad_click": 106,
                      "click_less_to": "not_sent",
                      "enabled_meta_rules": 0,
                      "source": "engine",
                      "truncated": false,
                      "stages": [
                        {
                          "stage": "lead_created",
                          "label": "Contacto nuevo",
                          "on": true,
                          "total": 60,
                          "with_ad_click": 9,
                          "weekly": 14,
                          "weekly_with_ad_click": 2.1,
                          "measured_weeks": 4,
                          "measured_days": 30
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-sync/data-sources/whatsapp/ensure": {
      "post": {
        "summary": "Create the WhatsApp dataset in Meta (idempotent)",
        "description": "The Meta destinations of the workspace’s outcomes, each set up by its own explicit press, never as a side effect. Step 1 — the dataset linked to the WhatsApp Business Account — receives the outcomes carrying the click-to-WhatsApp ad click (`ctwa_clid`), as `business_messaging`; a click-to-WhatsApp ad set optimised on Purchase needs it. Step 2 — the website pixel — receives every other outcome, matched by the hashed phone and e-mail; without it those outcomes (most payments) are not sent at all. Step 3 — the dataset linked to the Instagram account — receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id (measurement only: Meta offers no Purchase optimisation on Instagram); until it exists they reach the website pixel as before. Each sends from its press on (no history), and nothing flows until the stages are switched on in «Enviar a Meta». Meta cannot delete a dataset. Needs the CURRENT `whatsapp.notice` (version + hash from the read): 409 `notice_required` otherwise. A retry answers `already_present`; a turned-off dataset is turned back on. 409 `details.reason`: `meta_ads_not_connected`, `whatsapp_not_connected`, `entitlement_required`, `duplicate_kind`, `dataset_unreadable`, `already_exists`, `engine_not_ready`. 502 when Meta failed (nothing was created). The answer carries the engine’s read-back. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notice_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`notice.version` from the read."
                  },
                  "notice_hash": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "`notice.hash` from the read: proof the notice was read."
                  }
                },
                "required": [
                  "notice_version",
                  "notice_hash"
                ],
                "additionalProperties": false
              },
              "example": {
                "notice_version": "meta-whatsapp-dataset-2026-10-02",
                "notice_hash": "0000000000000000000000000000000000000000000000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created (or already present), with the read-back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "created",
                            "already_present"
                          ]
                        },
                        "status": {
                          "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                        }
                      },
                      "required": [
                        "outcome",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "created",
                    "status": {
                      "prerequisites": {
                        "meta_ads_connected": true,
                        "whatsapp_connected": true,
                        "instagram_connected": true
                      },
                      "whatsapp": {
                        "kind": "whatsapp",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<dataset_id>",
                          "id_tail": "4821",
                          "export_from": "2026-10-02T15:00:00.000Z",
                          "destination_enabled": true,
                          "access_granted": true,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": true,
                        "notice": {
                          "version": "meta-whatsapp-dataset-2026-10-02",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "website": {
                        "kind": "website",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-website-pixel-2026-10-02",
                          "points": [
                            "Tu píxel del sitio recibe los resultados que no traen el clic…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        },
                        "candidates": [
                          {
                            "external_id": "<pixel_id>",
                            "id_tail": "9748",
                            "name": "Píxel del sitio",
                            "last_fired_at": "2026-10-01T22:10:00.000Z",
                            "recommended": true
                          }
                        ],
                        "can_create_pixel": false
                      },
                      "instagram": {
                        "kind": "instagram",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-instagram-dataset-2026-10-07",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "click_less_not_sent": true,
                      "preview": {
                        "window_days": 30,
                        "total": 120,
                        "with_ad_click": 14,
                        "without_ad_click": 106,
                        "click_less_to": "not_sent",
                        "enabled_meta_rules": 0,
                        "source": "engine",
                        "truncated": false,
                        "stages": [
                          {
                            "stage": "lead_created",
                            "label": "Contacto nuevo",
                            "on": true,
                            "total": 60,
                            "with_ad_click": 9,
                            "weekly": 14,
                            "weekly_with_ad_click": 2.1,
                            "measured_weeks": 4,
                            "measured_days": 30
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/data-sources/website/ensure": {
      "post": {
        "summary": "Connect the website pixel as the destination of click-less outcomes",
        "description": "The Meta destinations of the workspace’s outcomes, each set up by its own explicit press, never as a side effect. Step 1 — the dataset linked to the WhatsApp Business Account — receives the outcomes carrying the click-to-WhatsApp ad click (`ctwa_clid`), as `business_messaging`; a click-to-WhatsApp ad set optimised on Purchase needs it. Step 2 — the website pixel — receives every other outcome, matched by the hashed phone and e-mail; without it those outcomes (most payments) are not sent at all. Step 3 — the dataset linked to the Instagram account — receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id (measurement only: Meta offers no Purchase optimisation on Instagram); until it exists they reach the website pixel as before. Each sends from its press on (no history), and nothing flows until the stages are switched on in «Enviar a Meta». Meta cannot delete a dataset. `external_id` must be one of `website.candidates` (409 `not_a_pixel` otherwise). Needs the CURRENT `website.notice` (409 `notice_required`). 409 `engine_not_ready` while the measurement engine cannot wire a pixel yet («Pronto»). Turning the pixel off is «Enviar a Meta»’s `send_stop`. The answer carries the engine’s read-back. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notice_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`notice.version` from the read."
                  },
                  "notice_hash": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "`notice.hash` from the read: proof the notice was read."
                  },
                  "external_id": {
                    "type": "string",
                    "pattern": "^\\d{6,20}$",
                    "description": "The chosen pixel: one of `website.candidates[].external_id`."
                  }
                },
                "required": [
                  "notice_version",
                  "notice_hash",
                  "external_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "notice_version": "meta-website-pixel-2026-10-02",
                "notice_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                "external_id": "<pixel_id>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Connected (or already present), with the read-back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "created",
                            "already_present"
                          ]
                        },
                        "status": {
                          "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                        }
                      },
                      "required": [
                        "outcome",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "created",
                    "status": {
                      "prerequisites": {
                        "meta_ads_connected": true,
                        "whatsapp_connected": true,
                        "instagram_connected": true
                      },
                      "whatsapp": {
                        "kind": "whatsapp",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<dataset_id>",
                          "id_tail": "4821",
                          "export_from": "2026-10-02T15:00:00.000Z",
                          "destination_enabled": true,
                          "access_granted": true,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": true,
                        "notice": {
                          "version": "meta-whatsapp-dataset-2026-10-02",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "website": {
                        "kind": "website",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<pixel_id>",
                          "id_tail": "9748",
                          "export_from": "2026-10-02T15:05:00.000Z",
                          "destination_enabled": true,
                          "access_granted": null,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-website-pixel-2026-10-02",
                          "points": [
                            "Tu píxel del sitio recibe los resultados que no traen el clic…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        },
                        "candidates": [],
                        "can_create_pixel": false
                      },
                      "instagram": {
                        "kind": "instagram",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-instagram-dataset-2026-10-07",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "click_less_not_sent": false,
                      "preview": {
                        "window_days": 30,
                        "total": 120,
                        "with_ad_click": 14,
                        "without_ad_click": 106,
                        "click_less_to": "not_sent",
                        "enabled_meta_rules": 0,
                        "source": "engine",
                        "truncated": false,
                        "stages": [
                          {
                            "stage": "lead_created",
                            "label": "Contacto nuevo",
                            "on": true,
                            "total": 60,
                            "with_ad_click": 9,
                            "weekly": 14,
                            "weekly_with_ad_click": 2.1,
                            "measured_weeks": 4,
                            "measured_days": 30
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/data-sources/website/create-pixel": {
      "post": {
        "summary": "Create a pixel in the ad account and connect it as the website pixel («Crear píxel»)",
        "description": "The Meta destinations of the workspace’s outcomes, each set up by its own explicit press, never as a side effect. Step 1 — the dataset linked to the WhatsApp Business Account — receives the outcomes carrying the click-to-WhatsApp ad click (`ctwa_clid`), as `business_messaging`; a click-to-WhatsApp ad set optimised on Purchase needs it. Step 2 — the website pixel — receives every other outcome, matched by the hashed phone and e-mail; without it those outcomes (most payments) are not sent at all. Step 3 — the dataset linked to the Instagram account — receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id (measurement only: Meta offers no Purchase optimisation on Instagram); until it exists they reach the website pixel as before. Each sends from its press on (no history), and nothing flows until the stages are switched on in «Enviar a Meta». Meta cannot delete a dataset. For an ad account with no pixel (`website.can_create_pixel`): the measurement engine creates ONE pixel named «<workspace> · Vitrina» in the ad account (or adopts a pixel already carrying that name) and connects it exactly as `website/ensure` does. Meta cannot delete a pixel; a retry replays (idempotent per workspace and name) and answers `already_present`. Needs the CURRENT `website.notice` (409 `notice_required`). 409 `details.reason`: `website_pixel_connected` (another pixel is already connected; nothing created), `pixel_slot_taken` (Meta allows one self-created pixel per ad account and it is used: connect it with `website/ensure`), `pixel_already_attached`, `meta_ads_not_connected`, `meta_write_permission_missing` (reconnect Meta Ads granting every permission), `reconnect_required`, `entitlement_required`, `engine_not_ready` («Pronto»). 502 when Meta failed (nothing was created). The answer carries the engine’s read-back. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notice_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`notice.version` from the read."
                  },
                  "notice_hash": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "`notice.hash` from the read: proof the notice was read."
                  }
                },
                "required": [
                  "notice_version",
                  "notice_hash"
                ],
                "additionalProperties": false
              },
              "example": {
                "notice_version": "meta-website-pixel-2026-10-02",
                "notice_hash": "0000000000000000000000000000000000000000000000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created (or already present) and connected, with the read-back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "created",
                            "already_present"
                          ]
                        },
                        "status": {
                          "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                        }
                      },
                      "required": [
                        "outcome",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "created",
                    "status": {
                      "prerequisites": {
                        "meta_ads_connected": true,
                        "whatsapp_connected": true,
                        "instagram_connected": true
                      },
                      "whatsapp": {
                        "kind": "whatsapp",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<dataset_id>",
                          "id_tail": "4821",
                          "export_from": "2026-10-02T15:00:00.000Z",
                          "destination_enabled": true,
                          "access_granted": true,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": true,
                        "notice": {
                          "version": "meta-whatsapp-dataset-2026-10-02",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "website": {
                        "kind": "website",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<new_pixel_id>",
                          "id_tail": "7890",
                          "export_from": "2026-10-02T15:05:00.000Z",
                          "destination_enabled": true,
                          "access_granted": null,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-website-pixel-2026-10-02",
                          "points": [
                            "Tu píxel del sitio recibe los resultados que no traen el clic…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        },
                        "candidates": [],
                        "can_create_pixel": false
                      },
                      "instagram": {
                        "kind": "instagram",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-instagram-dataset-2026-10-07",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "click_less_not_sent": false,
                      "preview": {
                        "window_days": 30,
                        "total": 120,
                        "with_ad_click": 14,
                        "without_ad_click": 106,
                        "click_less_to": "not_sent",
                        "enabled_meta_rules": 0,
                        "source": "engine",
                        "truncated": false,
                        "stages": [
                          {
                            "stage": "lead_created",
                            "label": "Contacto nuevo",
                            "on": true,
                            "total": 60,
                            "with_ad_click": 9,
                            "weekly": 14,
                            "weekly_with_ad_click": 2.1,
                            "measured_weeks": 4,
                            "measured_days": 30
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/data-sources/instagram/ensure": {
      "post": {
        "summary": "Create the Instagram dataset in Meta (idempotent)",
        "description": "The Meta destinations of the workspace’s outcomes, each set up by its own explicit press, never as a side effect. Step 1 — the dataset linked to the WhatsApp Business Account — receives the outcomes carrying the click-to-WhatsApp ad click (`ctwa_clid`), as `business_messaging`; a click-to-WhatsApp ad set optimised on Purchase needs it. Step 2 — the website pixel — receives every other outcome, matched by the hashed phone and e-mail; without it those outcomes (most payments) are not sent at all. Step 3 — the dataset linked to the Instagram account — receives the outcomes of conversations an Instagram ad opened, as `business_messaging` with the customer’s Instagram-scoped id (measurement only: Meta offers no Purchase optimisation on Instagram); until it exists they reach the website pixel as before. Each sends from its press on (no history), and nothing flows until the stages are switched on in «Enviar a Meta». Meta cannot delete a dataset. The measurement engine asks Meta to create the dataset linked to the connected Instagram account, and creates its Instagram destination; an existing destination is never repointed. Needs the CURRENT `instagram.notice` (version + hash from the read): 409 `notice_required` otherwise. A retry answers `already_present`; a turned-off dataset is turned back on. 409 `details.reason`: `meta_ads_not_connected`, `instagram_not_connected`, `entitlement_required`, `duplicate_kind`, `dataset_unreadable`, `already_exists`, `engine_not_ready` (the engine cannot create it yet: «Pronto»), `meta_permission_missing` (Meta refused for a missing app permission — `instagram_manage_events`), `reconnect_required` (the Instagram connection expired), `instagram_facebook_login_required` (the Instagram account is connected with Instagram Login; Meta creates the dataset only from a Facebook Login for Business connection linked to a Page — the read-back already carries it as the step's `reason_code`, with no press offered); nothing is created on any refusal. 502 when Meta failed (nothing was created). The answer carries the engine’s read-back. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "notice_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`notice.version` from the read."
                  },
                  "notice_hash": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$",
                    "description": "`notice.hash` from the read: proof the notice was read."
                  }
                },
                "required": [
                  "notice_version",
                  "notice_hash"
                ],
                "additionalProperties": false
              },
              "example": {
                "notice_version": "meta-instagram-dataset-2026-10-07",
                "notice_hash": "0000000000000000000000000000000000000000000000000000000000000000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created (or already present), with the read-back.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "outcome": {
                          "type": "string",
                          "enum": [
                            "created",
                            "already_present"
                          ]
                        },
                        "status": {
                          "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                        }
                      },
                      "required": [
                        "outcome",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "created",
                    "status": {
                      "prerequisites": {
                        "meta_ads_connected": true,
                        "whatsapp_connected": true,
                        "instagram_connected": true
                      },
                      "whatsapp": {
                        "kind": "whatsapp",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-whatsapp-dataset-2026-10-02",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "website": {
                        "kind": "website",
                        "state": "ready",
                        "reason_code": null,
                        "message": null,
                        "dataset": null,
                        "can_create": true,
                        "can_turn_off": false,
                        "notice": {
                          "version": "meta-website-pixel-2026-10-02",
                          "points": [
                            "Tu píxel del sitio recibe los resultados que no traen el clic…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        },
                        "candidates": [
                          {
                            "external_id": "<pixel_id>",
                            "id_tail": "9748",
                            "name": "Píxel del sitio",
                            "last_fired_at": "2026-10-01T22:10:00.000Z",
                            "recommended": true
                          }
                        ],
                        "can_create_pixel": false
                      },
                      "instagram": {
                        "kind": "instagram",
                        "state": "created",
                        "reason_code": null,
                        "message": null,
                        "dataset": {
                          "external_id": "<dataset_id>",
                          "id_tail": "3307",
                          "export_from": "2026-10-07T15:00:00.000Z",
                          "destination_enabled": true,
                          "access_granted": true,
                          "shared": false,
                          "shared_count": null
                        },
                        "can_create": false,
                        "can_turn_off": true,
                        "notice": {
                          "version": "meta-instagram-dataset-2026-10-07",
                          "points": [
                            "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                          ],
                          "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                        }
                      },
                      "click_less_not_sent": false,
                      "preview": {
                        "window_days": 30,
                        "total": 120,
                        "with_ad_click": 14,
                        "without_ad_click": 106,
                        "click_less_to": "not_sent",
                        "enabled_meta_rules": 0,
                        "source": "engine",
                        "truncated": false,
                        "stages": [
                          {
                            "stage": "lead_created",
                            "label": "Contacto nuevo",
                            "on": true,
                            "total": 60,
                            "with_ad_click": 9,
                            "weekly": 14,
                            "weekly_with_ad_click": 2.1,
                            "measured_weeks": 4,
                            "measured_days": 30
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/data-sources/instagram/turn-off": {
      "post": {
        "summary": "Stop sending to the Instagram dataset",
        "description": "Turns the Instagram dataset’s destination off: nothing more reaches it, and the Instagram-ad outcomes go back to the website pixel. The dataset stays in the Meta account (Meta cannot delete one); creating it again turns it back on. 409 `not_sending` when there is nothing to turn off. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The read-back after turning it off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "prerequisites": {
                      "meta_ads_connected": true,
                      "whatsapp_connected": true,
                      "instagram_connected": true
                    },
                    "whatsapp": {
                      "kind": "whatsapp",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-whatsapp-dataset-2026-10-02",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "website": {
                      "kind": "website",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-website-pixel-2026-10-02",
                        "points": [
                          "Tu píxel del sitio recibe los resultados que no traen el clic…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      },
                      "candidates": [
                        {
                          "external_id": "<pixel_id>",
                          "id_tail": "9748",
                          "name": "Píxel del sitio",
                          "last_fired_at": "2026-10-01T22:10:00.000Z",
                          "recommended": true
                        }
                      ],
                      "can_create_pixel": false
                    },
                    "instagram": {
                      "kind": "instagram",
                      "state": "off",
                      "reason_code": null,
                      "message": null,
                      "dataset": {
                        "external_id": "<dataset_id>",
                        "id_tail": "3307",
                        "export_from": "2026-10-07T15:00:00.000Z",
                        "destination_enabled": false,
                        "access_granted": true,
                        "shared": false,
                        "shared_count": null
                      },
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-instagram-dataset-2026-10-07",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "click_less_not_sent": false,
                    "preview": {
                      "window_days": 30,
                      "total": 120,
                      "with_ad_click": 14,
                      "without_ad_click": 106,
                      "click_less_to": "not_sent",
                      "enabled_meta_rules": 0,
                      "source": "engine",
                      "truncated": false,
                      "stages": [
                        {
                          "stage": "lead_created",
                          "label": "Contacto nuevo",
                          "on": true,
                          "total": 60,
                          "with_ad_click": 9,
                          "weekly": 14,
                          "weekly_with_ad_click": 2.1,
                          "measured_weeks": 4,
                          "measured_days": 30
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/conversion-sync/data-sources/whatsapp/turn-off": {
      "post": {
        "summary": "Stop sending to the WhatsApp dataset",
        "description": "Turns the dataset’s destination off: nothing more reaches it. The dataset stays in the Meta account (Meta cannot delete one); creating it again turns it back on. 409 `not_sending` when there is nothing to turn off. Requires `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The read-back after turning it off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMetaDatasetStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "prerequisites": {
                      "meta_ads_connected": true,
                      "whatsapp_connected": true,
                      "instagram_connected": true
                    },
                    "whatsapp": {
                      "kind": "whatsapp",
                      "state": "off",
                      "reason_code": null,
                      "message": null,
                      "dataset": {
                        "external_id": "<dataset_id>",
                        "id_tail": "4821",
                        "export_from": "2026-10-02T15:00:00.000Z",
                        "destination_enabled": false,
                        "access_granted": true,
                        "shared": false,
                        "shared_count": null
                      },
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-whatsapp-dataset-2026-10-02",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu WhatsApp Business…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "website": {
                      "kind": "website",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-website-pixel-2026-10-02",
                        "points": [
                          "Tu píxel del sitio recibe los resultados que no traen el clic…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      },
                      "candidates": [
                        {
                          "external_id": "<pixel_id>",
                          "id_tail": "9748",
                          "name": "Píxel del sitio",
                          "last_fired_at": "2026-10-01T22:10:00.000Z",
                          "recommended": true
                        }
                      ],
                      "can_create_pixel": false
                    },
                    "instagram": {
                      "kind": "instagram",
                      "state": "ready",
                      "reason_code": null,
                      "message": null,
                      "dataset": null,
                      "can_create": true,
                      "can_turn_off": false,
                      "notice": {
                        "version": "meta-instagram-dataset-2026-10-07",
                        "points": [
                          "Meta crea un conjunto de datos conectado a tu cuenta de Instagram…"
                        ],
                        "hash": "0000000000000000000000000000000000000000000000000000000000000000"
                      }
                    },
                    "click_less_not_sent": false,
                    "preview": {
                      "window_days": 30,
                      "total": 120,
                      "with_ad_click": 14,
                      "without_ad_click": 106,
                      "click_less_to": "not_sent",
                      "enabled_meta_rules": 0,
                      "source": "engine",
                      "truncated": false,
                      "stages": [
                        {
                          "stage": "lead_created",
                          "label": "Contacto nuevo",
                          "on": true,
                          "total": 60,
                          "with_ad_click": 9,
                          "weekly": 14,
                          "weekly_with_ad_click": 2.1,
                          "measured_weeks": 4,
                          "measured_days": 30
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payments": {
      "get": {
        "summary": "Money that arrived — the Transferencias queue, or one contact",
        "description": "`payments:read`. Without filters, the tenant's recent payments newest first. `unapplied=true` is the **Transferencias queue**: `Σ live allocations < amount_clp`, i.e. money we hold that is not fully spoken for — it covers all three shapes staff sees (no payer, a payer and no allocation, partly applied with a remainder), and `unidentified=true` narrows it to the first. Every row carries `unallocated_clp`, `suggested_contact_id` and `payer_reasons` (the matcher's stable codes, rendered as chips) plus its full allocation trail, released rows included — the Historial tab draws payment → allocations → obligations with no request per row. Reversed money is EXCLUDED unless `include_reversed=true`: reversed money is not money we hold.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              ]
            },
            "required": false,
            "name": "unapplied",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              ]
            },
            "required": false,
            "name": "unidentified",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              ]
            },
            "required": false,
            "name": "include_reversed",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Payments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e86256b2-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_recorded_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "transferencia",
                      "amount_clp": 50000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 50000,
                      "unallocated_clp": 0,
                      "bank": "Banco de Chile",
                      "account_number": null,
                      "document_number": "884213",
                      "operation_id": null,
                      "note": "Abono de la primera cuota",
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": null,
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:16.954Z",
                      "reversed": false,
                      "reversed_at": null,
                      "reversal_reason": null,
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000001",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000001",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 50000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_manual"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:16.990Z",
                          "released_at": null,
                          "release_reason": null,
                          "live": true
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 50,
                      "offset": 0,
                      "total": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record money by hand: cash, a card, a bono, a cheque",
        "description": "`payments:write`. Credits the ledger with `provenance: \"manual\"` — ADR 0086's distinction as a value: a human said so. `verified` means A BANK SAID SO and is unreachable through this door by construction (the database binds it to a comprobante or a gateway transaction).\n\nWHAT IT IS FOR, in precedence order: an explicit `allocations` split is applied VERBATIM; `obligation_id` fills that one cobro up to its outstanding; otherwise the planner proposes the same split the matcher would have applied, and `auto_allocate: false` credits the money and applies nothing. Anything left over stays the contact's CREDIT — a first-class outcome, never an error. `contact_id` may be null: money whose payer is not yet known is recorded, not refused.\n\nTHE CARD RECARGO IS RESOLVED SERVER-SIDE. On a `tarjeta_debito` / `tarjeta_credito` payment the workspace’s configured rate for THAT card type (`tenant.settings.card_fees` — débito ≠ crédito) is applied and FROZEN on the row as `card_fee_bps` + `card_surcharge_clp` + `card_surcharge_source: \"calculado\"`, and the response carries all three. Send neither field and that is what you get; send `card_fee_bps` to override the RATE for one swipe (`0` is a rate and means a $0 recargo, never \"unset\"); send `card_surcharge_clp` to override the FIGURE, which freezes `\"ajustado\"`. A workspace with no rate configured for that card type — and the legacy `tarjeta` member, whose card type nobody recorded — records NO recargo at all, never a $0 one. ⚠ THE RECARGO IS WHAT THE CUSTOMER PAYS ON TOP: it is NOT part of `amount_clp` and is allocated to nothing.\n\nSAY WHAT SALE IT WAS (`sale`, optional). A back office that records one sale per call sends `sale: { ref, place?, items? }`: `ref` is the sale’s id in YOUR system (two payments with the same `ref` are ONE sale, a deposit and its balance; different refs are different sales even minutes apart); `place: \"store\"` marks a counter sale whatever the instrument, so a transfer taken at the till is still a store sale for ad reporting; `items` (`[{ external_id, quantity?, unit_price_clp? }]`, `external_id` = your stable item id) is what was sold, sent on to the ad platforms as the Purchase’s `content_ids` / `contents`. Nothing else reads it.\n\n⚠ `Idempotency-Key` IS STORED ON THE ROW, not merely cached: a repeat returns **200** with the existing payment and `meta.replayed: true` instead of 409ing, because a receptionist who clicked twice asked one question. A first write answers 201. 409 when the split over-allocates the payment (`error.details.unallocated_clp` says what was left), when the cobro is closed, or when it belongs to somebody else.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "source": {
                    "type": "string",
                    "enum": [
                      "manual",
                      "card_terminal",
                      "credit_note"
                    ]
                  },
                  "instrument": {
                    "type": "string",
                    "enum": [
                      "efectivo",
                      "transferencia",
                      "cheque",
                      "vale_vista",
                      "tarjeta",
                      "tarjeta_debito",
                      "tarjeta_credito",
                      "bono",
                      "otro",
                      "mercadopago",
                      "webpay",
                      "fintoc",
                      "flow",
                      "khipu",
                      "bancame"
                    ]
                  },
                  "amount_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "paid_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "bank": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "account_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "document_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "sale": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "ref": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "place": {
                        "type": "string",
                        "enum": [
                          "store",
                          "online"
                        ]
                      },
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "external_id": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "quantity": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 10000
                            },
                            "unit_price_clp": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 999999999999
                            }
                          },
                          "required": [
                            "external_id"
                          ],
                          "additionalProperties": false
                        },
                        "minItems": 1,
                        "maxItems": 20
                      }
                    },
                    "required": [
                      "ref"
                    ],
                    "additionalProperties": false
                  },
                  "card_fee_bps": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "card_surcharge_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "location_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "allocations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "obligation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "amount_clp": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999999999999
                        }
                      },
                      "required": [
                        "obligation_id",
                        "amount_clp"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  },
                  "obligation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "auto_allocate": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "instrument",
                  "amount_clp",
                  "paid_on"
                ],
                "additionalProperties": false
              },
              "example": {
                "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "instrument": "transferencia",
                "amount_clp": 50000,
                "paid_on": "2026-09-24",
                "bank": "Banco de Chile",
                "document_number": "884213",
                "note": "Abono de la primera cuota",
                "allocations": [
                  {
                    "obligation_id": "b44936d0-0000-4000-8000-000000000004",
                    "amount_clp": 50000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment (idempotent replay)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "payment": {
                      "id": "e86256b2-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_recorded_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "transferencia",
                      "amount_clp": 50000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 50000,
                      "unallocated_clp": 0,
                      "bank": "Banco de Chile",
                      "account_number": null,
                      "document_number": "884213",
                      "operation_id": null,
                      "note": "Abono de la primera cuota",
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": null,
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:16.954Z",
                      "reversed": false,
                      "reversed_at": null,
                      "reversal_reason": null,
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000001",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000001",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 50000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_manual"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:16.990Z",
                          "released_at": null,
                          "release_reason": null,
                          "live": true
                        }
                      ]
                    },
                    "applied": [
                      {
                        "obligationId": "b44936d0-0000-4000-8000-000000000004",
                        "amountClp": 50000,
                        "allocatedClp": 50000,
                        "outstandingClp": 58000,
                        "expectedClp": 108000,
                        "state": "partial",
                        "label": "Ortodoncia — primera cuota"
                      }
                    ],
                    "surplus_clp": 0,
                    "reasons": [
                      "payer_recorded_by_staff",
                      "allocation_staff_manual"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/payments/instrument-fields": {
      "get": {
        "summary": "Which fields each payment instrument requires",
        "description": "`payments:read`, `sale_notes:write` or `reservations:write` (any one). The BR-261/BR-447 table every payment guard reads — `POST /payments`, the `funding` of `POST /sale-notes` and the MCP tools refuse a payment whose `required_fields` are blank. A form reads it to count what is still missing before submitting, rather than keeping its own copy. Static vocabulary: the same for every workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "responses": {
          "200": {
            "description": "One row per instrument, in vocabulary order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PaymentInstrumentRequirement"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "instrument": "efectivo",
                      "required_fields": []
                    },
                    {
                      "instrument": "transferencia",
                      "required_fields": []
                    },
                    {
                      "instrument": "cheque",
                      "required_fields": [
                        "bank",
                        "account_number",
                        "document_number"
                      ]
                    },
                    {
                      "instrument": "vale_vista",
                      "required_fields": []
                    },
                    {
                      "instrument": "tarjeta",
                      "required_fields": []
                    },
                    {
                      "instrument": "tarjeta_debito",
                      "required_fields": []
                    },
                    {
                      "instrument": "tarjeta_credito",
                      "required_fields": []
                    },
                    {
                      "instrument": "bono",
                      "required_fields": [
                        "document_number"
                      ]
                    },
                    {
                      "instrument": "otro",
                      "required_fields": [
                        "note"
                      ]
                    },
                    {
                      "instrument": "mercadopago",
                      "required_fields": []
                    },
                    {
                      "instrument": "webpay",
                      "required_fields": []
                    },
                    {
                      "instrument": "fintoc",
                      "required_fields": []
                    },
                    {
                      "instrument": "flow",
                      "required_fields": []
                    },
                    {
                      "instrument": "khipu",
                      "required_fields": []
                    },
                    {
                      "instrument": "bancame",
                      "required_fields": []
                    }
                  ],
                  "meta": {
                    "total": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payments/{id}": {
      "get": {
        "summary": "One payment with its full allocation trail",
        "description": "`payments:read`. Released allocations are INCLUDED and flagged `live: false`: the trail is the readable history of where a peso WAS applied, which is the question a dispute months later actually asks.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e86256b2-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "suggested_contact_id": null,
                    "payer_reasons": [
                      "payer_recorded_by_staff"
                    ],
                    "source": "manual",
                    "provenance": "manual",
                    "instrument": "transferencia",
                    "amount_clp": 50000,
                    "paid_on": "2026-09-24",
                    "allocated_clp": 50000,
                    "unallocated_clp": 0,
                    "bank": "Banco de Chile",
                    "account_number": null,
                    "document_number": "884213",
                    "operation_id": null,
                    "note": "Abono de la primera cuota",
                    "card_fee_bps": null,
                    "card_surcharge_clp": null,
                    "card_surcharge_source": null,
                    "location_id": null,
                    "bank_movement_id": null,
                    "gateway_transaction_id": null,
                    "recorded_by": null,
                    "recorded_at": "2026-09-22T22:21:16.954Z",
                    "reversed": false,
                    "reversed_at": null,
                    "reversal_reason": null,
                    "allocations": [
                      {
                        "id": "e8df2087-0000-4000-8000-000000000001",
                        "customer_payment_id": "e86256b2-0000-4000-8000-000000000001",
                        "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                        "amount_clp": 50000,
                        "method": "staff",
                        "reasons": [
                          "allocation_staff_manual"
                        ],
                        "allocated_by": null,
                        "allocated_at": "2026-09-22T22:21:16.990Z",
                        "released_at": null,
                        "release_reason": null,
                        "live": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payments/{id}/allocations": {
      "post": {
        "summary": "Allocate — or re-allocate — a payment across obligations",
        "description": "`payments:write`. Writes one `payment_allocation` row per line, with `method: \"staff\"`. Passing `release` makes it a RE-allocation: the named rows are released FIRST, in the same transaction, because nothing is ever edited in place — a split is changed by releasing and writing anew, so the history stays readable.\n\n409 `over_allocation` when the lines exceed what is left on the payment (`error.details.unallocated_clp` carries the figure, so the screen can say «solo quedan $5.000 sin asignar»), `obligation_not_open`, `contact_mismatch` (one person's money never settles another's debt — a row Postgres refuses, not a rule a service remembers), `payment_reversed`, `payer_unknown`. Re-sending an identical split is a no-op, not a double-allocation.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "allocations": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "obligation_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "amount_clp": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999999999999
                        }
                      },
                      "required": [
                        "obligation_id",
                        "amount_clp"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 50
                  },
                  "release": {
                    "type": "object",
                    "properties": {
                      "allocation_ids": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "minItems": 1,
                        "maxItems": 50
                      },
                      "reason": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 280
                      }
                    },
                    "required": [
                      "allocation_ids",
                      "reason"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "allocations"
                ],
                "additionalProperties": false
              },
              "example": {
                "allocations": [
                  {
                    "obligation_id": "b44936d0-0000-4000-8000-000000000004",
                    "amount_clp": 20000
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "payment": {
                      "id": "e86256b2-0000-4000-8000-000000000002",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_recorded_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "efectivo",
                      "amount_clp": 20000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 20000,
                      "unallocated_clp": 0,
                      "bank": null,
                      "account_number": null,
                      "document_number": null,
                      "operation_id": null,
                      "note": null,
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:17.052Z",
                      "reversed": false,
                      "reversed_at": null,
                      "reversal_reason": null,
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000002",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000002",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 20000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_accepted_plan",
                            "allocation_partial"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:17.057Z",
                          "released_at": null,
                          "release_reason": null,
                          "live": true
                        }
                      ]
                    },
                    "applied": [
                      {
                        "obligationId": "b44936d0-0000-4000-8000-000000000004",
                        "amountClp": 20000,
                        "allocatedClp": 90000,
                        "outstandingClp": 18000,
                        "expectedClp": 108000,
                        "state": "partial",
                        "label": "Ortodoncia — primera cuota"
                      }
                    ],
                    "surplus_clp": 0,
                    "reasons": [
                      "allocation_staff_manual"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payments/{id}/allocations/{allocationId}": {
      "delete": {
        "summary": "Release one allocation, with a reason",
        "description": "`payments:write`. Stamps `released_at / released_by / release_reason`; the row SURVIVES. `reason` is required (the table requires it) — money un-applied with no stated reason hands the next person the same question with no more information than the first person had. 409 `already_released`, 404 for an allocation that belongs to another payment (never a 403, which would confirm it exists).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "allocationId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Se imputó a la obligación equivocada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "payment": {
                      "id": "e86256b2-0000-4000-8000-000000000002",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_recorded_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "efectivo",
                      "amount_clp": 20000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 0,
                      "unallocated_clp": 20000,
                      "bank": null,
                      "account_number": null,
                      "document_number": null,
                      "operation_id": null,
                      "note": null,
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:17.052Z",
                      "reversed": false,
                      "reversed_at": null,
                      "reversal_reason": null,
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000002",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000002",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 20000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_accepted_plan",
                            "allocation_partial"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:17.057Z",
                          "released_at": "2026-09-22T22:21:17.094Z",
                          "release_reason": "Se imputó a la obligación equivocada",
                          "live": false
                        }
                      ]
                    },
                    "released": {
                      "id": "e8df2087-0000-4000-8000-000000000002",
                      "customer_payment_id": "e86256b2-0000-4000-8000-000000000002",
                      "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                      "amount_clp": 20000,
                      "method": "staff",
                      "reasons": [
                        "allocation_staff_accepted_plan",
                        "allocation_partial"
                      ],
                      "allocated_by": null,
                      "allocated_at": "2026-09-22T22:21:17.057Z",
                      "released_at": "2026-09-22T22:21:17.094Z",
                      "release_reason": "Se imputó a la obligación equivocada",
                      "live": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/payments/{id}/reverse": {
      "post": {
        "summary": "Reverse a payment — the trio, and every allocation released",
        "description": "⚠ `payments:reverse`, THE ONLY ROUTE ON THAT SCOPE. A reversal is the one act here that makes money the platform already told a customer about stop existing, and a workspace must be able to let somebody record money without letting them erase it.\n\nWrites `reversed_at / reversed_by / reversal_reason` AND releases every live allocation, in ONE transaction: two statements that could commit separately would leave a reversed payment still settling obligations. ⚠ IT DOES NOT RE-IMPOSE THE `awaiting_payment` BLOCK (§5.1, open question 9.3): the customer was already told the money arrived, and the platform has no honest sentence for \"we un-saw your transfer\". 409 `already_reversed` — reversing twice never rewrites the first reason.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El depósito fue rechazado por el banco"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "payment": {
                      "id": "e86256b2-0000-4000-8000-000000000002",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_assigned_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "efectivo",
                      "amount_clp": 20000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 0,
                      "unallocated_clp": 20000,
                      "bank": null,
                      "account_number": null,
                      "document_number": null,
                      "operation_id": null,
                      "note": null,
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:17.052Z",
                      "reversed": true,
                      "reversed_at": "2026-09-22T22:21:17.114Z",
                      "reversal_reason": "El depósito fue rechazado por el banco",
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000002",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000002",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 20000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_accepted_plan",
                            "allocation_partial"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:17.057Z",
                          "released_at": "2026-09-22T22:21:17.094Z",
                          "release_reason": "Se imputó a la obligación equivocada",
                          "live": false
                        }
                      ]
                    },
                    "released": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/payments/{id}/assign-payer": {
      "post": {
        "summary": "Say whose money a payment is (or take the name off it)",
        "description": "`payments:write`. The one field of a payment that is ever edited (§2.2): everything else about a received amount is immutable, and a wrong amount is a reversal plus a new row.\n\n⚠ RE-IDENTIFYING A PAYER THAT ALREADY HAS LIVE ALLOCATIONS RELEASES THEM FIRST, and the response says which. The identity FK cascades `contact_id` onto the allocations, and dragging them onto a new person while their obligations still belong to the old one is a row Postgres refuses — releasing states the truth instead. `contact_id: null` puts the money back in the unidentified queue (the reject half of a suggestion).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "contact_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "contact_id": "1a73af9e-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Payment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "payment": {
                      "id": "e86256b2-0000-4000-8000-000000000002",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "suggested_contact_id": null,
                      "payer_reasons": [
                        "payer_assigned_by_staff"
                      ],
                      "source": "manual",
                      "provenance": "manual",
                      "instrument": "efectivo",
                      "amount_clp": 20000,
                      "paid_on": "2026-09-24",
                      "allocated_clp": 0,
                      "unallocated_clp": 20000,
                      "bank": null,
                      "account_number": null,
                      "document_number": null,
                      "operation_id": null,
                      "note": null,
                      "card_fee_bps": null,
                      "card_surcharge_clp": null,
                      "card_surcharge_source": null,
                      "location_id": "b1b1b1b1-0000-4000-8000-000000000002",
                      "bank_movement_id": null,
                      "gateway_transaction_id": null,
                      "recorded_by": null,
                      "recorded_at": "2026-09-22T22:21:17.052Z",
                      "reversed": false,
                      "reversed_at": null,
                      "reversal_reason": null,
                      "allocations": [
                        {
                          "id": "e8df2087-0000-4000-8000-000000000002",
                          "customer_payment_id": "e86256b2-0000-4000-8000-000000000002",
                          "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                          "amount_clp": 20000,
                          "method": "staff",
                          "reasons": [
                            "allocation_staff_accepted_plan",
                            "allocation_partial"
                          ],
                          "allocated_by": null,
                          "allocated_at": "2026-09-22T22:21:17.057Z",
                          "released_at": "2026-09-22T22:21:17.094Z",
                          "release_reason": "Se imputó a la obligación equivocada",
                          "live": false
                        }
                      ]
                    },
                    "released": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/obligations": {
      "get": {
        "summary": "What is owed, by state, contact, kind or group",
        "description": "`payments:read`. ⚠ `state` MIXES A COLUMN AND A DERIVATION. `cancelled` and `waived` are stored; `open` is the stored status; `pending`, `partial`, `paid`, `overpaid`, `overdue` and `overdue_partial` are COMPUTED from allocations and exist nowhere in the schema — nothing sweeps them, so a late payment always finds a live obligation to settle. The server pushes down what SQL can answer and applies the derived half over a bounded window, reported as `meta.derived_filter_window`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "partial",
                "paid",
                "overpaid",
                "overdue",
                "overdue_partial",
                "cancelled",
                "waived",
                "open"
              ]
            },
            "required": false,
            "name": "state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "appointment_deposit",
                "service",
                "pack",
                "product",
                "vehicle_reservation_deposit",
                "sale_note",
                "consignment_settlement",
                "real_estate_reserva",
                "real_estate_pie",
                "installment",
                "other"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "group_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Obligations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b44936d0-0000-4000-8000-000000000004",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "service",
                      "direction": "receivable",
                      "label": "Ortodoncia — primera cuota",
                      "expected_clp": 112000,
                      "allocated_clp": 0,
                      "outstanding_clp": 112000,
                      "overpaid_clp": 0,
                      "state": "pending",
                      "provenance": null,
                      "is_overdue": false,
                      "status": "open",
                      "disposition": null,
                      "opened_on": "2026-09-22",
                      "due_on": "2026-10-10",
                      "closed_at": null,
                      "closed_reason": null,
                      "group_id": null,
                      "source": "staff",
                      "document": null,
                      "allocations": []
                    },
                    {
                      "id": "b44936d0-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "pack",
                      "direction": "receivable",
                      "label": "Pack 4 controles de ortodoncia",
                      "expected_clp": 96000,
                      "allocated_clp": 0,
                      "outstanding_clp": 96000,
                      "overpaid_clp": 0,
                      "state": "pending",
                      "provenance": null,
                      "is_overdue": false,
                      "status": "open",
                      "disposition": null,
                      "opened_on": "2026-09-22",
                      "due_on": null,
                      "closed_at": null,
                      "closed_reason": null,
                      "group_id": "a067dce6-0000-4000-8000-000000000001",
                      "source": "staff",
                      "document": null,
                      "allocations": []
                    },
                    {
                      "id": "b44936d0-0000-4000-8000-000000000002",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "service",
                      "direction": "receivable",
                      "label": "Control de ortodoncia",
                      "expected_clp": 112000,
                      "allocated_clp": 0,
                      "outstanding_clp": 112000,
                      "overpaid_clp": 0,
                      "state": "pending",
                      "provenance": null,
                      "is_overdue": false,
                      "status": "open",
                      "disposition": null,
                      "opened_on": "2026-09-22",
                      "due_on": null,
                      "closed_at": null,
                      "closed_reason": null,
                      "group_id": "a067dce6-0000-4000-8000-000000000002",
                      "source": "staff",
                      "document": null,
                      "allocations": []
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 50,
                      "offset": 0,
                      "total": 4
                    },
                    "derived_filter_window": 500
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record what somebody owes",
        "description": "`payments:write`. The variable-amount case staff type by hand — \"Tratamiento conducto, $80.000, vence en 7 días\". `label` is FROZEN at creation (ADR 0057): renaming a servicio next month must not rewrite what this patient was quoted. The three document-anchored kinds require their document, and writing one twice for the same document yields ONE expectation — a partial unique index, not a convention.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "appointment_deposit",
                      "service",
                      "pack",
                      "product",
                      "vehicle_reservation_deposit",
                      "sale_note",
                      "consignment_settlement",
                      "real_estate_reserva",
                      "real_estate_pie",
                      "installment",
                      "other"
                    ]
                  },
                  "expected_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  },
                  "opened_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "group_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "reservation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "sale_note_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "required": [
                  "contact_id",
                  "kind",
                  "expected_clp",
                  "label"
                ],
                "additionalProperties": false
              },
              "example": {
                "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "kind": "service",
                "expected_clp": 112000,
                "label": "Ortodoncia — primera cuota",
                "due_on": "2026-10-10"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Obligation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b44936d0-0000-4000-8000-000000000004",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "service",
                    "direction": "receivable",
                    "label": "Ortodoncia — primera cuota",
                    "expected_clp": 112000,
                    "allocated_clp": 0,
                    "outstanding_clp": 112000,
                    "overpaid_clp": 0,
                    "state": "pending",
                    "provenance": null,
                    "is_overdue": false,
                    "status": "open",
                    "disposition": null,
                    "opened_on": "2026-09-22",
                    "due_on": "2026-10-10",
                    "closed_at": null,
                    "closed_reason": null,
                    "group_id": null,
                    "source": "staff",
                    "document": null,
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/obligations/groups/{groupId}": {
      "get": {
        "summary": "A presupuesto, pack or plan and every line in it",
        "description": "`payments:read`. The group carries NO total: `expected_clp`, `allocated_clp`, `outstanding_clp` and the headline `state` are derived from its lines every time they are asked for, because a stored total is BR-249's defect with a friendlier name. Lines come back in the order money fills them (`due_on` ascending, deadline-less last).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "groupId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "today",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Obligation group",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a067dce6-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "pack",
                    "label": "Pack 4 controles de ortodoncia",
                    "status": "open",
                    "obligation_count": 1,
                    "expected_clp": 96000,
                    "allocated_clp": 0,
                    "outstanding_clp": 96000,
                    "state": "pending",
                    "obligations": [
                      {
                        "id": "b44936d0-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "pack",
                        "direction": "receivable",
                        "label": "Pack 4 controles de ortodoncia",
                        "expected_clp": 96000,
                        "allocated_clp": 0,
                        "outstanding_clp": 96000,
                        "overpaid_clp": 0,
                        "state": "pending",
                        "provenance": null,
                        "is_overdue": false,
                        "status": "open",
                        "disposition": null,
                        "opened_on": "2026-09-22",
                        "due_on": null,
                        "closed_at": null,
                        "closed_reason": null,
                        "group_id": "a067dce6-0000-4000-8000-000000000001",
                        "source": "staff",
                        "document": null,
                        "allocations": []
                      }
                    ],
                    "created_at": "2026-09-22T22:21:14.760Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/obligations/{id}": {
      "get": {
        "summary": "One cobro, its derived state and its allocation trail",
        "description": "`payments:read`. `state` and `outstanding_clp` are computed on read; `provenance` rides alongside the state so the badge keeps ADR 0086's split after the rename of `verified` to `paid` — green means a bank said so, violet means a human did, and one hand-asserted peso in the total grades the whole figure down to `manual`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "today",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Obligation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b44936d0-0000-4000-8000-000000000004",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "service",
                    "direction": "receivable",
                    "label": "Ortodoncia — primera cuota",
                    "expected_clp": 112000,
                    "allocated_clp": 0,
                    "outstanding_clp": 112000,
                    "overpaid_clp": 0,
                    "state": "pending",
                    "provenance": null,
                    "is_overdue": false,
                    "status": "open",
                    "disposition": null,
                    "opened_on": "2026-09-22",
                    "due_on": "2026-10-10",
                    "closed_at": null,
                    "closed_reason": null,
                    "group_id": null,
                    "source": "staff",
                    "document": null,
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Adjust what is owed, its deadline or its label — with a reason",
        "description": "`payments:write`. ⚠ `expected_clp` IS MUTABLE AND THAT IS CORRECT: the clinic quoted $15.000 for the evaluación, the patient chose two sessions, the expectation becomes $80.000 — and the allocations already made are untouched and still valid, because only the DERIVED outstanding changes. What must not happen is silently: `reason` is required and the change writes `audit_log` (`obligations.adjust`) in the SAME transaction, so a crash cannot separate the amount from why it moved. A closed cobro is a 409: its amount and its plazo are history.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "expected_clp": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "expected_clp": 108000,
                "reason": "Se aplicó el descuento del convenio"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Obligation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b44936d0-0000-4000-8000-000000000004",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "service",
                    "direction": "receivable",
                    "label": "Ortodoncia — primera cuota",
                    "expected_clp": 108000,
                    "allocated_clp": 0,
                    "outstanding_clp": 108000,
                    "overpaid_clp": 0,
                    "state": "pending",
                    "provenance": null,
                    "is_overdue": false,
                    "status": "open",
                    "disposition": null,
                    "opened_on": "2026-09-22",
                    "due_on": "2026-10-10",
                    "closed_at": null,
                    "closed_reason": null,
                    "group_id": null,
                    "source": "staff",
                    "document": null,
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/obligations/{id}/waive": {
      "post": {
        "summary": "Forgive what is owed, with a reason",
        "description": "`payments:write`. One of the three facts allocations cannot express, which is why `status` is a column at all. It does NOT release the pesos already applied: money that was applied was applied, the derivation stops counting the line as outstanding, and moving those pesos elsewhere is a release — its own verb, its own reason. 409 when the cobro is already closed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Cortesía de la clínica por la espera"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Obligation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "03e03646-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "service",
                    "direction": "receivable",
                    "label": "Control extra",
                    "expected_clp": 12000,
                    "allocated_clp": 0,
                    "outstanding_clp": 0,
                    "overpaid_clp": 0,
                    "state": "waived",
                    "provenance": null,
                    "is_overdue": false,
                    "status": "waived",
                    "disposition": null,
                    "opened_on": "2026-09-22",
                    "due_on": null,
                    "closed_at": "2026-09-22T22:21:17.162Z",
                    "closed_reason": "Cortesía de la clínica por la espera",
                    "group_id": null,
                    "source": "staff",
                    "document": null,
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/obligations/{id}/cancel": {
      "post": {
        "summary": "Call off a cobro, and say what became of the money",
        "description": "`payments:write`. `disposition` is BR-266/267's vocabulary verbatim (`aplicado | devuelto | perdido`) and is ORTHOGONAL to the status: a cancelled reserva whose seña was KEPT is `status: cancelled` + `disposition: perdido`. Collapsing the two is how a dealership loses track of money it decided to keep. 409 when the cobro is already closed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  },
                  "disposition": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "aplicado",
                      "devuelto",
                      "perdido"
                    ]
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "La prestación no se realizó",
                "disposition": "devuelto"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Obligation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "4fd0653c-0000-4000-8000-000000000001",
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "kind": "service",
                    "direction": "receivable",
                    "label": "Control anulado",
                    "expected_clp": 9000,
                    "allocated_clp": 0,
                    "outstanding_clp": 0,
                    "overpaid_clp": 0,
                    "state": "cancelled",
                    "provenance": null,
                    "is_overdue": false,
                    "status": "cancelled",
                    "disposition": "devuelto",
                    "opened_on": "2026-09-22",
                    "due_on": null,
                    "closed_at": "2026-09-22T22:21:17.176Z",
                    "closed_reason": "La prestación no se realizó",
                    "group_id": null,
                    "source": "staff",
                    "document": null,
                    "allocations": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/obligations/{id}/checkout": {
      "post": {
        "summary": "A Mercado Pago payment link for what one cobro still owes",
        "description": "`payments:write`. Creates (or returns the existing) Checkout Pro link from the business's OWN connected Mercado Pago account for this obligation's outstanding amount, for any kind (deposit, service, pack, product, nota de venta, imported balance). Calling again returns the same link while the amount still matches. The item title is generic (never the obligation label). Installments are offered only for a final charge at or above the business threshold (`mp_installments_min_clp`). When paid, the money is applied to this obligation automatically and the customer returns to `receipt_url`. 409 with `details.reason`: `not_connected` (no Mercado Pago account connected), `nothing_outstanding` / `already_paid`, `not_open` (closed, or its earlier link was cancelled), `not_receivable`, `no_contact`. 404 when it does not exist; 502 `provider_unavailable` when Mercado Pago could not be reached (retry).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "checkout_id": "0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                    "checkout_url": "https://www.mercadopago.cl/checkout/v1/redirect?pref_id=123456789-abc",
                    "amount_clp": 45000,
                    "installments": false,
                    "receipt_url": "https://app.example.com/pago/0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                    "obligation_id": "0192f6a0-11aa-7c22-8d33-4e5f60718293",
                    "group_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/obligations/groups/{groupId}/checkout": {
      "post": {
        "summary": "A Mercado Pago payment link for a presupuesto, pack or plan",
        "description": "`payments:write`. One link for a whole group, or for a chosen partial (`amount_clp`, e.g. a presupuesto's first instalment, 1..outstanding). When paid, the money fills the group's obligations in the ledger order (nearest due date first) and nothing outside the group. One open link per group: asking again with the same amount returns it, a different amount replaces it (the old link stops taking money). 409 with `details.reason`: `not_connected` (no Mercado Pago account connected), `nothing_outstanding` / `already_paid`, `not_open` (closed, or its earlier link was cancelled), `not_receivable`, `no_contact`. 404 when it does not exist; 502 `provider_unavailable` when Mercado Pago could not be reached (retry). 400 `amount_out_of_range` when `amount_clp` exceeds the outstanding.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "groupId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_clp": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "description": "Whole CLP to charge now, 1..the group outstanding. Omitted = the whole outstanding.",
                    "example": 150000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "amount_clp": 150000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The payment link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "checkout_id": "0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                    "checkout_url": "https://www.mercadopago.cl/checkout/v1/redirect?pref_id=123456789-abc",
                    "amount_clp": 150000,
                    "installments": true,
                    "receipt_url": "https://app.example.com/pago/0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                    "obligation_id": null,
                    "group_id": "0192f6a0-22bb-7d33-8e44-5f60718293a4"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contacts/{id}/ledger": {
      "get": {
        "summary": "Everything one person owes, has paid, and has in their favour",
        "description": "`payments:read` — NOT `contacts:read`: what a person owes and has paid is strictly more than the ficha's identity surface.\n\nTHE ONE SHARED READ (§6.1): the ficha's Pagos tab, the agenda drawer and the agent's bounded read tool all consume this shape. `credit_clp` is the unallocated surplus (Σ live payments − Σ live allocations) and is NEVER a column; `outstanding_clp` sums the open obligations. It reads the WHOLE set and does not page, deliberately — a paged read would compute a credit that is too small, and do it silently.\n\n`pending_verification` is EVIDENCE, NOT MONEY: payments whose payer is only a SUGGESTION for this contact. The platform has seen something arrive and has not attributed it, and stating it as received would be the platform confirming a transfer nobody has matched.\n\nEVERY `payments[]` ROW CARRIES ITS CARD RECARGO — `card_surcharge_clp` (what the customer paid ON TOP, so the screen can draw «Recargo tarjeta $X»), `card_fee_bps` (the rate it was frozen at) and `card_surcharge_source` (`calculado` = that rate applied, `ajustado` = a figure an operator typed). ⚠ IT IS NOT PART OF `amount_clp` AND NOT PART OF ANY TOTAL HERE: `credit_clp`, `allocated_clp` and `outstanding_clp` are all net of it, because the recargo covers the card machine and settles nothing. A screen that wants what the terminal charged adds the two itself; a screen that wants what was paid towards the cobro uses `amount_clp` alone.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": false,
            "name": "today",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact ledger",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                    "credit_clp": 0,
                    "outstanding_clp": 296000,
                    "obligations": [
                      {
                        "id": "b44936d0-0000-4000-8000-000000000004",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "service",
                        "direction": "receivable",
                        "label": "Ortodoncia — primera cuota",
                        "expected_clp": 108000,
                        "allocated_clp": 50000,
                        "outstanding_clp": 58000,
                        "overpaid_clp": 0,
                        "state": "partial",
                        "provenance": "manual",
                        "is_overdue": false,
                        "status": "open",
                        "disposition": null,
                        "opened_on": "2026-09-22",
                        "due_on": "2026-10-10",
                        "closed_at": null,
                        "closed_reason": null,
                        "group_id": null,
                        "source": "staff",
                        "document": null,
                        "allocations": [
                          {
                            "id": "e8df2087-0000-4000-8000-000000000001",
                            "customer_payment_id": "e86256b2-0000-4000-8000-000000000001",
                            "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                            "amount_clp": 50000,
                            "method": "staff",
                            "reasons": [
                              "allocation_staff_manual"
                            ],
                            "allocated_by": null,
                            "allocated_at": "2026-09-22T22:21:16.990Z",
                            "released_at": null,
                            "release_reason": null,
                            "live": true
                          }
                        ]
                      },
                      {
                        "id": "b44936d0-0000-4000-8000-000000000003",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "service",
                        "direction": "receivable",
                        "label": "Limpieza dental",
                        "expected_clp": 30000,
                        "allocated_clp": 0,
                        "outstanding_clp": 30000,
                        "overpaid_clp": 0,
                        "state": "pending",
                        "provenance": null,
                        "is_overdue": false,
                        "status": "open",
                        "disposition": null,
                        "opened_on": "2026-09-22",
                        "due_on": null,
                        "closed_at": null,
                        "closed_reason": null,
                        "group_id": "a067dce6-0000-4000-8000-000000000002",
                        "source": "staff",
                        "document": null,
                        "allocations": []
                      },
                      {
                        "id": "b44936d0-0000-4000-8000-000000000002",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "service",
                        "direction": "receivable",
                        "label": "Control de ortodoncia",
                        "expected_clp": 112000,
                        "allocated_clp": 0,
                        "outstanding_clp": 112000,
                        "overpaid_clp": 0,
                        "state": "pending",
                        "provenance": null,
                        "is_overdue": false,
                        "status": "open",
                        "disposition": null,
                        "opened_on": "2026-09-22",
                        "due_on": null,
                        "closed_at": null,
                        "closed_reason": null,
                        "group_id": "a067dce6-0000-4000-8000-000000000002",
                        "source": "staff",
                        "document": null,
                        "allocations": []
                      }
                    ],
                    "payments": [
                      {
                        "id": "e86256b2-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "suggested_contact_id": null,
                        "payer_reasons": [
                          "payer_recorded_by_staff"
                        ],
                        "source": "manual",
                        "provenance": "manual",
                        "instrument": "transferencia",
                        "amount_clp": 50000,
                        "paid_on": "2026-09-24",
                        "allocated_clp": 50000,
                        "unallocated_clp": 0,
                        "bank": "Banco de Chile",
                        "account_number": null,
                        "document_number": "884213",
                        "operation_id": null,
                        "note": "Abono de la primera cuota",
                        "card_fee_bps": null,
                        "card_surcharge_clp": null,
                        "card_surcharge_source": null,
                        "location_id": null,
                        "bank_movement_id": null,
                        "gateway_transaction_id": null,
                        "recorded_by": null,
                        "recorded_at": "2026-09-22T22:21:16.954Z",
                        "reversed": false,
                        "reversed_at": null,
                        "reversal_reason": null,
                        "allocations": [
                          {
                            "id": "e8df2087-0000-4000-8000-000000000001",
                            "customer_payment_id": "e86256b2-0000-4000-8000-000000000001",
                            "customer_obligation_id": "b44936d0-0000-4000-8000-000000000004",
                            "amount_clp": 50000,
                            "method": "staff",
                            "reasons": [
                              "allocation_staff_manual"
                            ],
                            "allocated_by": null,
                            "allocated_at": "2026-09-22T22:21:16.990Z",
                            "released_at": null,
                            "release_reason": null,
                            "live": true
                          }
                        ]
                      }
                    ],
                    "pending_verification": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/installment-plans": {
      "get": {
        "summary": "List installment plans",
        "description": "`payments:read`. Plans newest first, each with its cuotas. Filter by `contact_id` or `external_ref`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "external_ref",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Installment plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "id": "0192f0b2-0000-7000-8000-00000000c000",
                    "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                    "label": "Plan de cuotas",
                    "external_ref": "CONTRATO-2291",
                    "currency": "CLP",
                    "count": 6,
                    "total_amount": 510000,
                    "paid_amount": 85000,
                    "outstanding": 425000,
                    "status": "active",
                    "installments": [
                      {
                        "id": "0192f0b2-0000-7000-8000-00000000c001",
                        "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                        "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                        "number": 2,
                        "count": 6,
                        "amount": 85000,
                        "currency": "CLP",
                        "due_date": "2026-11-05",
                        "status": "overdue",
                        "ledger_state": "overdue",
                        "paid_amount": 0,
                        "outstanding": 85000,
                        "paid_at": null,
                        "days_overdue": 4,
                        "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                      }
                    ],
                    "created_at": "2026-10-05T14:00:00.000Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an installment plan",
        "description": "`payments:write`. A customer owes money in cuotas. Send the debtor (`contact_id`, or `rut` / `phone` / `email` to find or create them) and either the cuotas (`installments`: amount, due date, optional own `payment_link_url`) or an even `schedule` (`total_amount`, `count`, `first_due_date`: monthly, the last cuota absorbs the rounding). `external_ref` is your own reference (contract, loan): it must be unique (409 otherwise) and is what the import matches on. Amounts are whole CLP. A plan is an obligation group on the payments ledger and each cuota one of its obligations: paid and overdue are derived from payments, never set. Nothing here sends a message: a debtor who is not a contact yet is created silently (no `new_contact` trigger).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "rut": {
                    "type": "string",
                    "maxLength": 20
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  },
                  "external_ref": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "installments": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "number": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 120
                        },
                        "amount": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999999999999
                        },
                        "due_date": {
                          "type": "string",
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        },
                        "payment_link_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 2000,
                          "format": "uri",
                          "pattern": "^https?:\\/\\/"
                        }
                      },
                      "required": [
                        "amount",
                        "due_date"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 120
                  },
                  "schedule": {
                    "type": "object",
                    "properties": {
                      "total_amount": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 999999999999
                      },
                      "count": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 120
                      },
                      "first_due_date": {
                        "type": "string",
                        "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                      }
                    },
                    "required": [
                      "total_amount",
                      "count",
                      "first_due_date"
                    ]
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "rut": "12.345.678-5",
                "name": "Marta Rojas",
                "external_ref": "CONTRATO-2291",
                "schedule": {
                  "total_amount": 510000,
                  "count": 6,
                  "first_due_date": "2026-10-05"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The plan with its cuotas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "label": "Plan de cuotas",
                  "external_ref": "CONTRATO-2291",
                  "currency": "CLP",
                  "count": 6,
                  "total_amount": 510000,
                  "paid_amount": 85000,
                  "outstanding": 425000,
                  "status": "active",
                  "installments": [
                    {
                      "id": "0192f0b2-0000-7000-8000-00000000c001",
                      "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                      "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                      "number": 2,
                      "count": 6,
                      "amount": 85000,
                      "currency": "CLP",
                      "due_date": "2026-11-05",
                      "status": "overdue",
                      "ledger_state": "overdue",
                      "paid_amount": 0,
                      "outstanding": 85000,
                      "paid_at": null,
                      "days_overdue": 4,
                      "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                    }
                  ],
                  "created_at": "2026-10-05T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/installment-plans/import": {
      "post": {
        "summary": "Import cuotas in bulk (JSON)",
        "description": "`payments:write`. One row per cuota (`external_ref`, `number`, `amount`, `due_date`, and the debtor as `contact_id` / `rut` / `phone` / `email` + `name`; optional `count`, `payment_link_url`, `label`, `paid_on`). Rows sharing an `external_ref` are one plan. **Idempotent on `external_ref`**: the same rows again change nothing; a new amount, date or link updates a cuota that is still owing; `paid_on` marks it paid; a paid cuota never moves. Up to 2000 rows. Row problems are listed in `errors` and do not stop the rest. Nothing here sends a message: a debtor who is not a contact yet is created silently (no `new_contact` trigger).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "contact_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "phone": {
                          "type": "string",
                          "maxLength": 40
                        },
                        "email": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "rut": {
                          "type": "string",
                          "maxLength": 20
                        },
                        "external_ref": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "number": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 120
                        },
                        "count": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 1,
                          "maximum": 120
                        },
                        "amount": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 999999999999
                        },
                        "due_date": {
                          "type": "string",
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        },
                        "payment_link_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 2000,
                          "format": "uri",
                          "pattern": "^https?:\\/\\/"
                        },
                        "label": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 280
                        },
                        "paid_on": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                        }
                      },
                      "required": [
                        "external_ref",
                        "number",
                        "amount",
                        "due_date"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 2000
                  }
                },
                "required": [
                  "rows"
                ],
                "additionalProperties": false
              },
              "example": {
                "rows": [
                  {
                    "external_ref": "CONTRATO-2291",
                    "number": 1,
                    "count": 6,
                    "amount": 85000,
                    "due_date": "2026-10-05",
                    "rut": "12.345.678-5",
                    "name": "Marta Rojas",
                    "paid_on": "2026-10-04"
                  },
                  {
                    "external_ref": "CONTRATO-2291",
                    "number": 2,
                    "amount": 85000,
                    "due_date": "2026-11-05",
                    "rut": "12.345.678-5"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the import did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "total_rows": 6,
                  "plans_created": 1,
                  "plans_updated": 0,
                  "installments_created": 6,
                  "installments_updated": 0,
                  "installments_unchanged": 0,
                  "marked_paid": 1,
                  "contacts_created": 1,
                  "errors": []
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/installment-plans/import-file": {
      "post": {
        "summary": "Import cuotas from a CSV file",
        "description": "`payments:write`. The same import as `POST /installment-plans/import`, from a CSV (`file`, up to 5 MB). Headers (Spanish or English): `external_ref`/`referencia`, `number`/`cuota`, `amount`/`monto`, `due_date`/`vencimiento`, and optional `count`, `rut`, `phone`/`telefono`, `email`/`correo`, `name`/`nombre`, `contact_id`, `payment_link_url`/`link_pago`, `label`, `paid_on`/`pagado_el`. Comma or semicolon separated; pesos as `$85.000`; dates as `2026-11-05` or `05/11/2026`. Nothing here sends a message: a debtor who is not a contact yet is created silently (no `new_contact` trigger).\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary CSV part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "cuotas.csv",
                "content": "external_ref,number,amount,due_date,rut,name\nCONTRATO-2291,1,85000,2026-10-05,12.345.678-5,Marta Rojas"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the import did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "total_rows": 6,
                  "plans_created": 1,
                  "plans_updated": 0,
                  "installments_created": 6,
                  "installments_updated": 0,
                  "installments_unchanged": 0,
                  "marked_paid": 1,
                  "contacts_created": 1,
                  "errors": []
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/installment-plans/{id}": {
      "get": {
        "summary": "Fetch one installment plan",
        "description": "`payments:read`. The plan with every cuota, its status and what is still owed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "label": "Plan de cuotas",
                  "external_ref": "CONTRATO-2291",
                  "currency": "CLP",
                  "count": 6,
                  "total_amount": 510000,
                  "paid_amount": 85000,
                  "outstanding": 425000,
                  "status": "active",
                  "installments": [
                    {
                      "id": "0192f0b2-0000-7000-8000-00000000c001",
                      "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                      "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                      "number": 2,
                      "count": 6,
                      "amount": 85000,
                      "currency": "CLP",
                      "due_date": "2026-11-05",
                      "status": "overdue",
                      "ledger_state": "overdue",
                      "paid_amount": 0,
                      "outstanding": 85000,
                      "paid_at": null,
                      "days_overdue": 4,
                      "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                    }
                  ],
                  "created_at": "2026-10-05T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Rename an installment plan",
        "description": "`payments:write`. Changes the plan label.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 280
                  }
                },
                "required": [
                  "label"
                ],
                "additionalProperties": false
              },
              "example": {
                "label": "Plan matrícula 2027"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "label": "Plan de cuotas",
                  "external_ref": "CONTRATO-2291",
                  "currency": "CLP",
                  "count": 6,
                  "total_amount": 510000,
                  "paid_amount": 85000,
                  "outstanding": 425000,
                  "status": "active",
                  "installments": [
                    {
                      "id": "0192f0b2-0000-7000-8000-00000000c001",
                      "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                      "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                      "number": 2,
                      "count": 6,
                      "amount": 85000,
                      "currency": "CLP",
                      "due_date": "2026-11-05",
                      "status": "overdue",
                      "ledger_state": "overdue",
                      "paid_amount": 0,
                      "outstanding": 85000,
                      "paid_at": null,
                      "days_overdue": 4,
                      "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                    }
                  ],
                  "created_at": "2026-10-05T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/installment-plans/{id}/cancel": {
      "post": {
        "summary": "Cancel an installment plan",
        "description": "`payments:write`. Cancels every cuota still owing (money already paid stays) and ends the person's collection sequence. Needs a `reason`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Se refinanció la deuda"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "label": "Plan de cuotas",
                  "external_ref": "CONTRATO-2291",
                  "currency": "CLP",
                  "count": 6,
                  "total_amount": 510000,
                  "paid_amount": 85000,
                  "outstanding": 425000,
                  "status": "active",
                  "installments": [
                    {
                      "id": "0192f0b2-0000-7000-8000-00000000c001",
                      "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                      "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                      "number": 2,
                      "count": 6,
                      "amount": 85000,
                      "currency": "CLP",
                      "due_date": "2026-11-05",
                      "status": "overdue",
                      "ledger_state": "overdue",
                      "paid_amount": 0,
                      "outstanding": 85000,
                      "paid_at": null,
                      "days_overdue": 4,
                      "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                    }
                  ],
                  "created_at": "2026-10-05T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/installments": {
      "get": {
        "summary": "List cuotas",
        "description": "`payments:read`. Cuotas across plans. `status` is `pending`, `paid`, `overdue` (past the due day and still owing) or `cancelled`; it is derived from payments, so filtering on it reads a bounded window of 500. Filter by `contact_id` or `plan_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "plan_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "paid",
                "overdue",
                "cancelled"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "default": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Cuotas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "id": "0192f0b2-0000-7000-8000-00000000c001",
                    "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                    "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                    "number": 2,
                    "count": 6,
                    "amount": 85000,
                    "currency": "CLP",
                    "due_date": "2026-11-05",
                    "status": "overdue",
                    "ledger_state": "overdue",
                    "paid_amount": 0,
                    "outstanding": 85000,
                    "paid_at": null,
                    "days_overdue": 4,
                    "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/installments/{id}": {
      "get": {
        "summary": "Fetch one cuota",
        "description": "`payments:read`. One cuota with its status, what is owed and its payment link.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The cuota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c001",
                  "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "number": 2,
                  "count": 6,
                  "amount": 85000,
                  "currency": "CLP",
                  "due_date": "2026-11-05",
                  "status": "overdue",
                  "ledger_state": "overdue",
                  "paid_amount": 0,
                  "outstanding": 85000,
                  "paid_at": null,
                  "days_overdue": 4,
                  "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Change a cuota",
        "description": "`payments:write`. Amount, due date and/or the payment link. Amount and date only while the cuota is open; changes are audited with `reason`. `payment_link_url: null` removes the link.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999999999999
                  },
                  "due_date": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "payment_link_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000,
                    "format": "uri",
                    "pattern": "^https?:\\/\\/"
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "due_date": "2026-11-20",
                "reason": "Pidió más plazo"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cuota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c001",
                  "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "number": 2,
                  "count": 6,
                  "amount": 85000,
                  "currency": "CLP",
                  "due_date": "2026-11-05",
                  "status": "overdue",
                  "ledger_state": "overdue",
                  "paid_amount": 0,
                  "outstanding": 85000,
                  "paid_at": null,
                  "days_overdue": 4,
                  "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/installments/{id}/mark-paid": {
      "post": {
        "summary": "Mark a cuota paid",
        "description": "`payments:write`. Records a payment for what is still owed on the cuota (`instrument` default `otro`, `paid_on` default today) and ends the person's collection sequence with the goal met. Already paid: nothing changes. A cancelled cuota answers 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "paid_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "instrument": {
                    "type": "string",
                    "enum": [
                      "efectivo",
                      "transferencia",
                      "cheque",
                      "vale_vista",
                      "tarjeta",
                      "tarjeta_debito",
                      "tarjeta_credito",
                      "bono",
                      "otro",
                      "mercadopago",
                      "webpay",
                      "fintoc",
                      "flow",
                      "khipu",
                      "bancame"
                    ]
                  },
                  "note": {
                    "type": "string",
                    "maxLength": 2000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "paid_on": "2026-11-09",
                "instrument": "transferencia"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cuota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "0192f0b2-0000-7000-8000-00000000c001",
                  "plan_id": "0192f0b2-0000-7000-8000-00000000c000",
                  "contact_id": "0192f0b2-0000-7000-8000-00000000a001",
                  "number": 2,
                  "count": 6,
                  "amount": 85000,
                  "currency": "CLP",
                  "due_date": "2026-11-05",
                  "status": "paid",
                  "ledger_state": "overdue",
                  "paid_amount": 0,
                  "outstanding": 85000,
                  "paid_at": null,
                  "days_overdue": 4,
                  "payment_link_url": "https://pagos.ejemplo.cl/c/8842"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-links/options": {
      "get": {
        "summary": "What a customer owes, and whether a payment link can be sent",
        "description": "`payments:read`. The customer's open receivable debt as payable targets, most urgent first: each presupuesto / pack / plan is ONE target that may be paid in part (`partial_allowed`), each cobro outside a group is charged exactly its outstanding. Per target: whether a link may be sent (`link_allowed`; `link_refusal` = `transfer_only` | `not_connected`) under the charging preference. `delivery`: the conversation the link would go through and its messaging window (`open`, `human_agent` = Instagram/Messenger 24 h–7 days, `closed` = copy the link instead).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The customer to charge."
            },
            "required": true,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The conversation the link would go through (the inbox). Omitted = the contact's most recent conversation."
            },
            "required": false,
            "name": "conversation_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Payable debt and delivery route",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "contact_id": "0192f6a0-0000-7000-8000-000000000001",
                    "connected": true,
                    "total_outstanding_clp": 365000,
                    "debts": [
                      {
                        "target": "group",
                        "id": "0192f6a0-22bb-7d33-8e44-5f60718293a4",
                        "kind": "presupuesto",
                        "label": "Presupuesto E-47",
                        "outstanding_clp": 320000,
                        "partial_allowed": true,
                        "charge_type": "final",
                        "due_on": null,
                        "is_overdue": false,
                        "open_checkout": null,
                        "link_allowed": true,
                        "link_refusal": null,
                        "charge_mode": "mp_first"
                      }
                    ],
                    "delivery": {
                      "conversation_id": "0192f6a0-3333-7000-8000-000000000002",
                      "channel": "whatsapp",
                      "window": "open",
                      "can_send": true,
                      "reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/payment-links": {
      "post": {
        "summary": "Send a customer a Mercado Pago link for one debt",
        "description": "`payments:write` (+ `messages:send` and `conversations:write` to deliver). Creates (or reuses) the link for one obligation (its outstanding) or one group (`amount_clp`, 1..outstanding) and sends a generic message with it through the conversation, as the caller. When the window is closed (WhatsApp 24 h; Instagram/Messenger 7 days for a person), nothing is sent: `delivery.status = not_sent`, `delivery.reason = outside_window`, and `link.checkout_url` + `text` are there to copy. `deliver: false` only creates the link. An API key or connected app passes the outbound policy (422 / 409, `acknowledge`; may answer `pending_approval`). 409 `transfer_only` when the business charges only by transfer; the checkout refusals of `POST /obligations/{id}/checkout` otherwise. Audited as `payments.link_sent`. Paid links settle onto the debt automatically.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Payments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "obligation_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "One cobro, charged exactly its outstanding. Exactly one of `obligation_id` / `group_id`."
                  },
                  "group_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "A presupuesto, pack or plan; `amount_clp` may be any part of its outstanding."
                  },
                  "amount_clp": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "description": "Whole CLP. For a group, 1..its outstanding (omitted = all of it). For an obligation it must equal the outstanding, or be omitted.",
                    "example": 120000
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The conversation to send it through. Omitted = the contact's most recent conversation."
                  },
                  "deliver": {
                    "type": "boolean",
                    "description": "`false` only creates the link (to copy). Default `true`: send it through the conversation when its window allows."
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Outbound-policy warnings (409) to go ahead over, for API keys and connected apps."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "group_id": "0192f6a0-22bb-7d33-8e44-5f60718293a4",
                "amount_clp": 120000,
                "conversation_id": "0192f6a0-3333-7000-8000-000000000002"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link and how it went out",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "link": {
                      "checkout_id": "0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                      "checkout_url": "https://www.mercadopago.cl/checkout/v1/redirect?pref_id=123456789-abc",
                      "amount_clp": 120000,
                      "installments": false,
                      "receipt_url": "https://app.example.com/pago/0192f6a1-7c3e-7b10-9a54-3c1d2e4f5a60",
                      "obligation_id": null,
                      "group_id": "0192f6a0-22bb-7d33-8e44-5f60718293a4"
                    },
                    "text": "Hola Ana, te enviamos el link para pagar $120.000 de Clínica Ejemplo:\nhttps://www.mercadopago.cl/checkout/v1/redirect?pref_id=123456789-abc\n\nApenas se acredite el pago, queda registrado automáticamente.",
                    "delivery": {
                      "status": "sent",
                      "reason": null,
                      "message_id": "0192f6a0-4444-7000-8000-000000000003",
                      "conversation_id": "0192f6a0-3333-7000-8000-000000000002",
                      "channel": "whatsapp",
                      "window": "open"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/personal-tokens": {
      "get": {
        "summary": "List personal tokens",
        "description": "Your own personal tokens in this workspace, newest first. Needs `personal_tokens:read`, which every built-in role carries. Pass `user_id` to read another member’s — that additionally needs `api_keys:read`, the same permission that lists the workspace’s API keys.\n\nRevoked tokens are left out unless you ask for them with `include_revoked=true`; EXPIRED ones are always listed, because \"it expired eight days ago\" is the answer to \"why did my script stop\". The plaintext secret is never returned — only `prefix`, its first characters, for telling tokens apart.\n\nA connected app’s access tokens are not listed here: they belong to the grant that issued them and are managed with it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "PersonalTokens"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "include_revoked",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Personal tokens",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "c2c2c2c2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "name": "Reportes semanales",
                      "prefix": "sk_7Qm4T",
                      "scopes": [
                        "leads:read",
                        "contacts:read"
                      ],
                      "created_at": "2026-09-21T13:40:02.118Z",
                      "last_used_at": "2026-09-22T09:15:44.301Z",
                      "expires_at": "2026-12-20T13:40:02.118Z",
                      "revoked_at": null,
                      "connector": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Mint a personal token (returns the plaintext secret once)",
        "description": "Mints a token that ACTS AS YOU. It is bound to your membership in this workspace: its effective permissions are the scopes below ∩ the ones your role holds at the moment of each request, and it reads exactly the records and sucursales you read. Narrow your role and every token acting as you narrows with it; lose the membership and the token stops working on the next request.\n\nOmit `scopes` to take everything your role has right now, which is what the dialog offers. A scope you do not hold is a 403 naming it, never a silent drop. `expires_at` is 90 days from now when you leave it out; send `null` for a token with no expiry.\n\nThis is the credential to use for your own scripts. An API key (`POST /api-keys`) is the workspace’s and survives you; a personal token is yours and does not.\n\nNeeds `personal_tokens:write`, which every built-in role carries — and a SIGNED-IN SESSION: a credential cannot mint its own successor.\n\nThe plaintext `secret` is in the 201 body and is never retrievable again.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "PersonalTokens"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_]*:[a-z][a-z0-9_]*$",
                      "enum": [
                        "ai_agents:read",
                        "ai_agents:write",
                        "ai_agents:simulate",
                        "conversations:read",
                        "conversations:write",
                        "contacts:read",
                        "contacts:write",
                        "tickets:read",
                        "tickets:write",
                        "tickets:claim",
                        "leads:read",
                        "leads:write",
                        "leads:intake",
                        "pipelines:read",
                        "pipelines:write",
                        "stages:read",
                        "stages:write",
                        "functions:read",
                        "functions:write",
                        "kb:read",
                        "kb:write",
                        "messages:read",
                        "messages:write",
                        "messages:send",
                        "tools:read",
                        "tags:read",
                        "tags:write",
                        "api_keys:read",
                        "api_keys:write",
                        "personal_tokens:read",
                        "personal_tokens:write",
                        "notifications:read",
                        "notifications:write",
                        "audit:read",
                        "analytics:read",
                        "analytics:write",
                        "dealer_reports:read",
                        "tenant:read",
                        "tenant:write",
                        "tenant:delete",
                        "webhooks:read",
                        "webhooks:write",
                        "messaging_accounts:read",
                        "messaging_accounts:write",
                        "provisioning:read",
                        "provisioning:write",
                        "voice_drift:read",
                        "worker_failures:read",
                        "worker_failures:write",
                        "dealer_sites:read",
                        "dealer_sites:write",
                        "mcp_servers:read",
                        "mcp_servers:write",
                        "memberships:read",
                        "memberships:write",
                        "teams:read",
                        "teams:write",
                        "help_centers:read",
                        "help_centers:write",
                        "labels:read",
                        "labels:write",
                        "macros:read",
                        "macros:write",
                        "slas:read",
                        "slas:write",
                        "routing:read",
                        "routing:write",
                        "triggers:read",
                        "triggers:write",
                        "followups:read",
                        "followups:write",
                        "followups:manage",
                        "custom_attributes:read",
                        "custom_attributes:write",
                        "companies:read",
                        "companies:write",
                        "roles:read",
                        "roles:write",
                        "integrations:read",
                        "integrations:write",
                        "clinic:read",
                        "clinic:write",
                        "clinic_patients:read",
                        "clinic_patients:write",
                        "clinic_record:read",
                        "clinic_record:write",
                        "clinic_money:read",
                        "clinic_money:write",
                        "clinic_admin:write",
                        "clinic_insights:read",
                        "finanzas:read",
                        "finanzas:write",
                        "finanzas_reports:read",
                        "finanzas:export",
                        "finanzas_admin:write",
                        "finanzas_patients:read",
                        "finanzas_payroll:read",
                        "sandbox:read",
                        "sandbox:write",
                        "ads:read",
                        "ads:write",
                        "healthatom:read",
                        "healthatom:write",
                        "reservo:read",
                        "reservo:write",
                        "rexplus:read",
                        "rexplus:write",
                        "billing:read",
                        "billing:write",
                        "copilot:read",
                        "copilot:write",
                        "erp_copilot:use",
                        "tool_credentials:read",
                        "tool_credentials:write",
                        "tool_invocations:read",
                        "corrections:read",
                        "corrections:write",
                        "marketplace:read",
                        "marketplace:write",
                        "stock:read",
                        "storefront_events:write",
                        "pricing:read",
                        "pricing:write",
                        "roi:read",
                        "appointments:read",
                        "appointments:write",
                        "appointments:delete",
                        "schedule_config:write",
                        "appointments:intake",
                        "appointment_types:read",
                        "appointment_types:write",
                        "service_lifecycle:read",
                        "service_lifecycle:write",
                        "outbound_approvals:read",
                        "outbound_approvals:write",
                        "outbound_holds:read",
                        "outbound_holds:write",
                        "campaigns:read",
                        "campaigns:write",
                        "consignments:read",
                        "consignments:write",
                        "stock_dedup:read",
                        "stock_dedup:write",
                        "stock_photo_mirror:read",
                        "stock_photo_mirror:write",
                        "stock_retirement:read",
                        "stock_retirement:write",
                        "dealership_economics:read",
                        "dealership_economics:write",
                        "document_templates:read",
                        "document_templates:write",
                        "vehicle_registry:read",
                        "vehicle_registry:write",
                        "vehicle_pipeline:read",
                        "vehicle_pipeline:write",
                        "sale_notes:read",
                        "sale_notes:write",
                        "sale_notes:void",
                        "transfer_cases:read",
                        "transfer_cases:write",
                        "credit_applications:read",
                        "credit_applications:write",
                        "seller_compensation:read",
                        "seller_compensation:write",
                        "sales_leaderboard:read",
                        "quotes:read",
                        "quotes:write",
                        "quotes:void",
                        "reservations:read",
                        "reservations:write",
                        "reservations:void",
                        "reservations:dispose_abono",
                        "organization_legal:read",
                        "organization_legal:write",
                        "purchase_notes:read",
                        "purchase_notes:write",
                        "purchase_notes:void",
                        "document_payments:read",
                        "document_payments:write",
                        "tenant_bank_accounts:read",
                        "tenant_bank_accounts:write",
                        "bank_movements:read",
                        "bank_movements:write",
                        "payments:read",
                        "payments:write",
                        "payments:reverse",
                        "orders:read",
                        "orders:write",
                        "orders:checkout",
                        "orders:refund",
                        "price_approval:read",
                        "price_approval:request",
                        "price_approval:approve",
                        "cotizacion_discount:approve",
                        "legal:read",
                        "legal:write"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 64
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Reportes semanales",
                "scopes": [
                  "leads:read",
                  "contacts:read"
                ],
                "expires_at": "2026-12-20T13:40:02.118Z"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The personal token, with its one-time secret",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "c2c2c2c2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "user_id": "11111111-0000-4000-8000-000000000001",
                    "name": "Reportes semanales",
                    "prefix": "sk_7Qm4T",
                    "scopes": [
                      "leads:read",
                      "contacts:read"
                    ],
                    "created_at": "2026-09-21T13:40:02.118Z",
                    "last_used_at": null,
                    "expires_at": "2026-12-20T13:40:02.118Z",
                    "revoked_at": null,
                    "connector": false,
                    "secret": "sk_7Qm4T…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/personal-tokens/{id}": {
      "delete": {
        "summary": "Revoke a personal token",
        "description": "Immediate and irreversible: every request bearing this token’s secret 401s from this point on. There is no un-revoke — mint a new token instead.\n\nNeeds `personal_tokens:write`. Your own, always; another member’s additionally needs `api_keys:write`. A token that does not exist, belongs to another workspace, is an API key, or is a connected app’s access token is a 404 alike — this route is not a probe for credentials it may not revoke.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "PersonalTokens"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/consignments": {
      "get": {
        "summary": "List consignment contracts",
        "description": "Filter by `vehicle_id` and/or `estado`, with `limit` / `offset`. No total is returned — the response carries the rows and nothing else.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "activo",
                "vendido",
                "devuelto",
                "vencido"
              ]
            },
            "required": false,
            "name": "estado",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Consignment contracts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d7d7d7d7-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                      "modalidad": "en_local",
                      "contract_structure": "mandato_con_representacion",
                      "comision_type": "percentage",
                      "comision_value": 10,
                      "minimo_clp": 8000000,
                      "vencimiento": "2026-12-31",
                      "estado": "activo",
                      "vencimiento_reminded_at": null,
                      "liquidacion_due_notified_at": null,
                      "created_at": "2026-08-20T10:00:00.000Z",
                      "updated_at": "2026-08-20T10:00:00.000Z",
                      "sale_iva_regime": "exento",
                      "not_on_lot": false
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a contract",
        "description": "Creates it already `activo` — there is no draft state — and ACTIVATION HAS SIDE EFFECTS on the vehicle: its `tenencia` becomes `consignacion`, and `minimo_clp`, if given, is written through as the vehicle’s `floor_price_clp`. That floor is load-bearing: the pricing watch clamps every suggestion up to it and refuses to apply below it, so the owner’s minimum is what stops an automated recommendation undercutting them. WITHOUT a `minimo_clp` the vehicle’s existing floor is left exactly as it was — a contract that names no minimum never clears one the dealer set by hand.\n\n**Only `vehicle_id` and `modalidad` are required.** `dueno_contact_id`, the `comision_type`/`comision_value` pair, `minimo_clp` and `vencimiento` are all completable later through `PATCH /consignments/{id}` — this is the *contrato mínimo*, the shape a dealer can actually produce while classifying stock at the counter. It is a real, active contract and it imposes the tenencia like any other; what is deferred is the terms. Until they are recorded the contract cannot be sold (no commission means no split to settle — 409) and its mandato cannot be printed, and `GET /consignments/{id}/documentation` reports exactly which fields are missing with `documentationComplete: false`. Completing them clears all three by itself; there is no flag to set.\n\n`comision_type` and `comision_value` go together or not at all: a value with no type is unreadable (is `5` five percent or five pesos?).\n\n**One active contract per vehicle.** A second is a 409, enforced both in the service and by a partial unique index, so the race loses too. The vehicle, and the owner contact when one is given, must belong to the workspace.\n\n`comision_type` decides how `comision_value` is read (a percentage or a fixed amount), which is what the Liquidación later subtracts. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "dueno_contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "modalidad": {
                    "type": "string",
                    "enum": [
                      "en_local",
                      "virtual"
                    ]
                  },
                  "comision_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "percentage",
                      "fixed"
                    ]
                  },
                  "comision_value": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "comision_mas_iva": {
                    "type": "boolean"
                  },
                  "minimo_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "minimo_effective_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "vencimiento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "sale_iva_regime": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "afecto",
                      "exento"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Internal free-text notes. Never printed on the document. Blank clears them."
                  }
                },
                "required": [
                  "vehicle_id",
                  "modalidad"
                ]
              },
              "example": {
                "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                "modalidad": "en_local",
                "comision_type": "percentage",
                "comision_value": 10,
                "minimo_clp": 8000000
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The active contract",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                    "modalidad": "en_local",
                    "contract_structure": "mandato_con_representacion",
                    "comision_type": "percentage",
                    "comision_value": 10,
                    "minimo_clp": 8000000,
                    "vencimiento": "2026-12-31",
                    "estado": "activo",
                    "vencimiento_reminded_at": null,
                    "liquidacion_due_notified_at": null,
                    "created_at": "2026-08-20T10:00:00.000Z",
                    "updated_at": "2026-08-20T10:00:00.000Z",
                    "sale_iva_regime": "exento",
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/consignments/by-vehicle/{vehicleId}": {
      "get": {
        "summary": "The active contract for one vehicle",
        "description": "Answers the question the agent and the test-drive flow actually ask: is this car ours to hand over? Returns the ACTIVE contract (or null) plus a `not_on_lot` flag.\n\nUse this rather than filtering the list by `vehicle_id`: a vehicle can have several historical contracts and only ever one active, and this endpoint is the one that resolves that.\n\n`mandato` is the active contract’s mandato (ADR 0114 §5): `sin_emitir` — approving the sale of this car is refused until it is emitted; `sin_firmar` — emitted, but no signed copy was filed in the car’s expediente (the car shows an alert; nothing is blocked); `firmado`. `null` when there is no active contract.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "vehicleId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The active contract and tenencia context",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "vehicleId": "e1e1e1e1-0000-4000-8000-000000000001",
                    "tenencia": "consignacion",
                    "tenenciaSource": "document",
                    "hasActiveContract": true,
                    "modalidad": "en_local",
                    "notOnLot": false,
                    "contract": {
                      "id": "d7d7d7d7-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                      "modalidad": "en_local",
                      "contract_structure": "mandato_con_representacion",
                      "comision_type": "percentage",
                      "comision_value": 10,
                      "minimo_clp": 8000000,
                      "vencimiento": "2026-12-31",
                      "estado": "activo",
                      "vencimiento_reminded_at": null,
                      "liquidacion_due_notified_at": null,
                      "created_at": "2026-08-20T10:00:00.000Z",
                      "updated_at": "2026-08-20T10:00:00.000Z",
                      "sale_iva_regime": "exento",
                      "not_on_lot": false
                    },
                    "mandato": {
                      "contractId": "d7d7d7d7-0000-4000-8000-000000000001",
                      "estado": "sin_firmar",
                      "emittedAt": "2026-09-10T14:00:00.000Z",
                      "signedAt": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/consignments/{id}": {
      "get": {
        "summary": "Fetch one contract",
        "description": "The contract as stored, whatever its state.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The contract",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                    "modalidad": "en_local",
                    "contract_structure": "mandato_con_representacion",
                    "comision_type": "percentage",
                    "comision_value": 10,
                    "minimo_clp": 8000000,
                    "vencimiento": "2026-12-31",
                    "estado": "activo",
                    "vencimiento_reminded_at": null,
                    "liquidacion_due_notified_at": null,
                    "created_at": "2026-08-20T10:00:00.000Z",
                    "updated_at": "2026-08-20T10:00:00.000Z",
                    "sale_iva_regime": "exento",
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Update a contract",
        "description": "Edits the terms, and can move `estado` — except to `vendido`, which is a **409** directing you to the sell action, so a sale can never be recorded without producing its Liquidación. Other transitions are checked against the state machine and an illegal one is also a 409.\n\nMoving `vencimiento` RE-ARMS the expiry reminder: the \"already reminded\" stamp is cleared, so a pushed-out date produces a fresh nudge rather than staying silent.\n\nSupplying `minimo_clp` RENEGOTIATES the mínimo rather than editing a column: it appends an immutable revision to the contract’s terms history (`GET /consignments/{id}/terms`), dated `minimo_effective_on` or today. The new value must DIFFER from the one currently governing — repeating it is a **409**, as is a second revision on a date already taken.\n\nA raised `minimo_clp` writes through to the vehicle’s floor while the contract is active — but **clearing it never clears the floor**, and neither does the contract ending. The vehicle has one floor column with no record of who set it, so removing it here could silently wipe a floor the dealer set by hand. Clear the vehicle’s floor deliberately if that is what you want.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "dueno_contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "modalidad": {
                    "type": "string",
                    "enum": [
                      "en_local",
                      "virtual"
                    ]
                  },
                  "comision_type": {
                    "type": "string",
                    "enum": [
                      "percentage",
                      "fixed"
                    ]
                  },
                  "comision_value": {
                    "type": "number",
                    "minimum": 0
                  },
                  "comision_mas_iva": {
                    "type": "boolean"
                  },
                  "minimo_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "minimo_effective_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "vencimiento": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "estado": {
                    "type": "string",
                    "enum": [
                      "activo",
                      "vendido",
                      "devuelto",
                      "vencido"
                    ]
                  },
                  "sale_iva_regime": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "afecto",
                      "exento"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000,
                    "description": "Internal free-text notes. Never printed on the document. Blank clears them."
                  }
                }
              },
              "example": {
                "minimo_clp": 8300000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated contract",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                    "modalidad": "en_local",
                    "contract_structure": "mandato_con_representacion",
                    "comision_type": "percentage",
                    "comision_value": 10,
                    "minimo_clp": 8300000,
                    "vencimiento": "2026-12-31",
                    "estado": "activo",
                    "vencimiento_reminded_at": null,
                    "liquidacion_due_notified_at": null,
                    "created_at": "2026-08-20T10:00:00.000Z",
                    "updated_at": "2026-08-20T10:00:00.000Z",
                    "sale_iva_regime": "exento",
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/consignments/{id}/return": {
      "post": {
        "summary": "Return the car to its owner",
        "description": "Moves the contract to `devuelto` and flips the vehicle’s `tenencia` back to `propio`. Exactly equivalent to `PATCH { estado: \"devuelto\" }` — it exists so the intent reads as a verb rather than a state assignment.\n\n`devuelto` is the only terminal state that restores `propio`: a sold or expired contract KEEPS `tenencia: consignacion`, because the car was sold on consignment and the history should say so.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The returned contract",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                    "modalidad": "en_local",
                    "contract_structure": "mandato_con_representacion",
                    "comision_type": "percentage",
                    "comision_value": 10,
                    "minimo_clp": 8000000,
                    "vencimiento": "2026-12-31",
                    "estado": "devuelto",
                    "vencimiento_reminded_at": null,
                    "liquidacion_due_notified_at": null,
                    "created_at": "2026-08-20T10:00:00.000Z",
                    "updated_at": "2026-09-15T09:00:00.000Z",
                    "sale_iva_regime": "exento",
                    "not_on_lot": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/consignments/{id}/sell": {
      "post": {
        "summary": "Sell the car and settle with the owner",
        "description": "One atomic action doing two things: moves the contract to `vendido`, and produces the **Liquidación** — sale price minus commission equals the amount owed to the owner, minus every retained cost line the owner bears (derived from the car's cost ledger, never sent; a 409 if they exceed the payout), plus the payment record. Requires `amount_venta_clp`; `paid_at` records when the owner was actually paid.\n\n**Only the medio de pago is required**: `payout_method` (transferencia, vale_vista or cheque). `payout_bank`, `payout_account` (the cuenta de destino — only its last four digits are stored and returned as `payout_account_last4`), `payout_operation_number` and `payout_paid_by` («pagado por», an active member of the workspace; defaults to whoever registers it) are optional. What was recorded prints on the liquidación with the payment and registration dates.\n\n**The commission can be set for this sale**: `comision_amount_clp` (net of IVA) replaces what the contract’s terms would compute — a minimum commission, a figure renegotiated at closing. The liquidación then records a fixed commission of that amount; the contract keeps its terms. To charge the owner an additional cost, record it on the car’s cost ledger with a `dueno` share before settling.\n\nLegal only from `activo`; anything else is a 409. Answers 201 with the contract and the Liquidación.\n\nIt does NOT close the vehicle’s side — the estadía close belongs to the vehicle lifecycle, so mark the vehicle sold separately.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_venta_clp": {
                    "type": "integer",
                    "exclusiveMinimum": 0
                  },
                  "paid_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "comision_amount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Comisión acordada para ESTA venta, neta de IVA, en pesos. Opcional: si se omite, se calcula con los términos del contrato. Cuando se envía, la liquidación la registra como comisión fija (el contrato no cambia) y el «+ IVA» sigue siendo el del contrato. No puede superar `amount_venta_clp`."
                  },
                  "payout_method": {
                    "type": "string",
                    "enum": [
                      "transferencia",
                      "vale_vista",
                      "cheque"
                    ],
                    "description": "Medio de pago al consignante: `transferencia`, `vale_vista` o `cheque`. Es el único dato del pago que se exige."
                  },
                  "payout_bank": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Banco por el que se pagó. Opcional."
                  },
                  "payout_account": {
                    "type": "string",
                    "maxLength": 40,
                    "description": "Cuenta de destino. Opcional. Solo se guardan sus últimos 4 dígitos (`payout_account_last4`), que es lo que muestran la app y la liquidación; si se envía, necesita al menos 4 dígitos."
                  },
                  "payout_operation_number": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "Número de operación del pago. Opcional."
                  },
                  "payout_paid_by": {
                    "type": "string",
                    "format": "uuid",
                    "description": "«Pagado por»: id de usuario de un miembro activo del equipo. Opcional: si se omite, queda quien registra la liquidación."
                  }
                },
                "required": [
                  "amount_venta_clp",
                  "payout_method"
                ]
              },
              "example": {
                "amount_venta_clp": 8900000,
                "paid_at": "2026-09-18T15:00:00.000Z",
                "payout_method": "transferencia",
                "payout_bank": "Banco de Chile",
                "payout_account": "00-123-45678-21",
                "payout_operation_number": "99887766",
                "payout_paid_by": "11111111-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The contract and its Liquidación",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "contract": {
                      "id": "d7d7d7d7-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "dueno_contact_id": "22222222-0000-4000-8000-000000000004",
                      "modalidad": "en_local",
                      "contract_structure": "mandato_con_representacion",
                      "comision_type": "percentage",
                      "comision_value": 10,
                      "minimo_clp": 8000000,
                      "vencimiento": "2026-12-31",
                      "estado": "vendido",
                      "vencimiento_reminded_at": null,
                      "liquidacion_due_notified_at": null,
                      "created_at": "2026-08-20T10:00:00.000Z",
                      "updated_at": "2026-08-20T10:00:00.000Z",
                      "sale_iva_regime": "exento",
                      "not_on_lot": false
                    },
                    "liquidacion": {
                      "id": "d8d8d8d8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "consignment_contract_id": "d7d7d7d7-0000-4000-8000-000000000001",
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "settlement_mode": "stated_commission",
                      "amount_venta_clp": 8900000,
                      "comision_type": "percentage",
                      "comision_value": 10,
                      "comision_amount_clp": 890000,
                      "monto_owner_clp": 8010000,
                      "owner_floor_clp": null,
                      "deducciones_clp": 0,
                      "deductions": [],
                      "retiro_motivo": null,
                      "retiro_by": null,
                      "payout_method": "transferencia",
                      "payout_bank": "Banco de Chile",
                      "payout_account_last4": "5821",
                      "payout_operation_number": "99887766",
                      "payout_paid_by": "11111111-0000-4000-8000-000000000001",
                      "payout_paid_by_name": "Ana Pérez",
                      "paid_at": "2026-09-18T15:00:00.000Z",
                      "created_at": "2026-09-18T15:00:05.000Z",
                      "updated_at": "2026-09-18T15:00:05.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/consignments/{id}/liquidacion": {
      "get": {
        "summary": "Fetch the settlement record",
        "description": "The persisted Liquidación for a contract. It only exists once the contract has been sold — this is a read of a record, not a projection computed on demand, so the figures are what was agreed at settlement even if the terms changed afterwards.\n\nIt carries how the owner was paid (`payout_*`, with `payout_paid_by_name` resolved for display). On a liquidación registered before the payout fields existed they are all `null`; since then `payout_method` is always set and each of the others is `null` when it was not recorded.\n\n`deducciones_clp` is the Σ of the dueño share of every retained cost line the settlement discharged, and `deductions` lists them one by one (`cost_entry_id`, `label`, `entry_date`, `dueno_clp`). Selling a consigned car deducts them all: `monto_owner_clp` is `amount_venta_clp` minus the comisión minus `deducciones_clp`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Consignments"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Liquidación record",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d8d8d8d8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "consignment_contract_id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "settlement_mode": "stated_commission",
                    "amount_venta_clp": 8900000,
                    "comision_type": "percentage",
                    "comision_value": 10,
                    "comision_amount_clp": 890000,
                    "monto_owner_clp": 8010000,
                    "owner_floor_clp": null,
                    "deducciones_clp": 0,
                    "deductions": [],
                    "retiro_motivo": null,
                    "retiro_by": null,
                    "payout_method": "transferencia",
                    "payout_bank": "Banco de Chile",
                    "payout_account_last4": "5821",
                    "payout_operation_number": "99887766",
                    "payout_paid_by": "11111111-0000-4000-8000-000000000001",
                    "payout_paid_by_name": "Ana Pérez",
                    "paid_at": "2026-09-18T15:00:00.000Z",
                    "created_at": "2026-09-18T15:00:05.000Z",
                    "updated_at": "2026-09-18T15:00:05.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations": {
      "get": {
        "summary": "List conversations",
        "description": "A page of the inbox, newest first. Each row is the `conversation` row with four joins the inbox draws from: `contact`, `ticket`, `lastMessage` and `tags`.\n\nTwo status axes, and they are NOT the same thing. `convStatus` filters the conversation's own lifecycle (`open` · `pending` · `snoozed` · `resolved` · `closed`); `status` filters the lifecycle of the TICKET it hangs off, so a thread with no ticket is invisible to it. `handler` is the third axis — who is answering (`bot` · `human` · `external`) — and is independent of both.\n\n`?include=counts` adds a top-level `counts` sibling of `data`/`meta` carrying the EXACT payload of `GET /conversations/counts` — `{ myOpen, myUnread, myMentions, unassigned }` — so a client refreshes its badges and its list in ONE request. The counts are scoped to the calling principal and are independent of every filter here, so the two routes always agree for the same caller. The field is ABSENT (never zeroed) if the counts query fails, so a hiccup degrades the badges rather than blanking the list; an unrecognised `include` value is a 400 rather than a silently ignored parameter.\n\n`filtered=true` is the only way to see quarantined threads, and only the literal string `true` opts in — every other value excludes them, which is the default.\n\nEach row also carries `marketplaceWindow` — `{ open, closedAt, closedReason, lastEvent, lastEventReason, lastEventAt }` on a `facebook_marketplace` conversation, `null` on every other channel. `open` is whether a reply typed in Vitrina can be delivered right now.\n\nEach row also carries `ad_origin` (the `AdOrigin` schema) — the Meta ad that opened the thread, in ONE shape whatever the channel (click-to-WhatsApp or an Instagram DM ad), or `null` for an organic thread. `platform` is the thread channel; `ad_external_id` is Meta's ad id (`null` for a boosted post); `ad_name` and `campaign_name` are `null` when the Ads read does not know them; `title` is the ADVERTISER'S copy, never the customer's words; `thumbnail_url` is Vitrina's durable copy of the creative when `media_archived` is `true`, else Meta's own link, which expires — render it with a fallback; `captured_at` is when the thread opened. Meta's own object stays on `ad_referral`, verbatim and write-once, in the channel's spelling (`source_id`/`ctwa_clid` on WhatsApp; `source: 'ADS'`, `ad_id`, `ads_context_data` on Instagram).\n\nEach row also carries `campaign_origin` — the newest campaign send stored in the thread (`null` when none): its campaign, when it left, whether the send created the thread, and whether it is still the newest message (`awaiting_reply`). `fromCampaign=true` lists every such thread, closed ones included.\n\nEach row also carries `waiting_since` — when its customer started waiting on a human, `null` when nobody waits (the «Esperando respuesta» rule; see the `waiting` parameter). `waiting=true` lists only waiting conversations, team-wide; `sort=wait` orders urgent first, then the longest wait. `waiting_state` says why: `owed` (waiting), `closed_by_customer` (the customer only acknowledged, thanked or closed after we answered), `nothing_owed` (they declined, or the message is not for us), `paid_or_in_store` (a transfer receipt right after we sent our bank details, or «llegué» / «voy subiendo»: a suggestion for a person to confirm — nothing moves a lead or records a payment) or `promised_later` (we promised to write on a later day: a dated «Recordatorio acordado» follow-up, due at `waiting_due_at`, after which the thread is waiting again) or `visit_passed` (the customer announced a store visit — «mañana paso», «el martes voy» — for a day that is now over, with nothing newer from them: a «Recordatorio acordado» to check whether they came falls due at `waiting_due_at`, the morning after; the thread does not come back to the queue when it does) or `customer_later` (the customer deferred — «la otra semana», «por la quincena me acerco», «el lunes te aviso» — and an open «Recordatorio acordado» covers it, due at `waiting_due_at`: out of the queue until the customer writes again or it falls due) — the ones left out of the queue by a confident verdict on the newest unanswered customer message, voice notes read as their transcript — and `null` when nobody waits at all. `waiting=closed` lists those left-out threads (still live: resolving stays a person’s click).\n\nRows here do NOT carry `ad_origin_nudge` — that field is single-conversation-read only, to avoid a per-row integration lookup on every page of the inbox.\n\n`fields=summary` answers the SAME page with only the identifying and funnel scalars per row — `id`, `display_id`, `channel`, `source`, `status`, `handler`, `assignee_user_id`, `contact_id`, `ticket_id`, `awaiting_human_since`, `first_ai_message_at`, `last_message_date`, `created_at`, `ad_origin` — plus `contact_name`, `ticket_status` and `current_stage_id` off the embeds, and `assignee_name` / `unread` when the list computed them (an api_key has no read markers, so no `unread`). No `contact`, `ticket`, `lastMessage`, `tags`, `marketplaceWindow` or `last_call`. `meta` and `counts` are unchanged. A 20-row page is ~147k characters at `full` (the default, byte-identical to omitting the parameter) and a fraction of that at `summary`; page with `summary` to choose, then `GET /conversations/{id}` for one thread whole.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "channel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "messagingAccountId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "conversation",
                "marketplace",
                "manual",
                "import",
                "ai_agent",
                "chileautos",
                "yapo",
                "mercadolibre",
                "website"
              ]
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "pending",
                "snoozed",
                "resolved",
                "closed"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "pending",
                "snoozed",
                "resolved",
                "closed"
              ]
            },
            "required": false,
            "name": "convStatus",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "bot",
                "human",
                "external"
              ]
            },
            "required": false,
            "name": "handler",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "assigneeUserId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "teamId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "ticketId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "currentStageId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "pipelineId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "fromDate",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "toDate",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "unread_only",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "mentioned_to_me",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "unassigned",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "filtered",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "aiReplied",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "fromAd",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "fromCampaign",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "closed"
                  ]
                },
                {
                  "type": "null"
                }
              ],
              "description": "true: only conversations where a customer is waiting on a human («Esperando respuesta»), team-wide. Waiting = open or pending, not filtered, on a channel you can reply in, and either an AI handoff nobody on the team has answered yet (a reply from ANY teammate clears it, including WhatsApp Business / Instagram app replies; a silent «Visita acordada» notice needs no answer) or a human-handled thread whose last message is the customer’s; waits older than 7 days are left out; and what the customer wrote still asks for a reply: a thread whose newest unanswered customer message was judged, with high confidence, as the customer closing the chat («ok», «gracias», 👍 after we answered) or owing nothing, a customer who paid (a transfer receipt after our bank details) or is at the store («llegué», «voy subiendo»), or a follow-up we promised for a later day that is not due yet, is left out (any other open promise of ours always stays); a customer who announced a store visit («mañana paso», «el martes voy») waits through that day and is left out once it is over with nothing newer from them (`visit_passed`: a «Recordatorio acordado» to check whether they came falls due the next morning). `closed`: exactly those left-out threads, still live (not resolved) — `waiting_state` says why (`closed_by_customer` | `nothing_owed` | `paid_or_in_store` | `promised_later` | `visit_passed`), `waiting_due_at` when a promised follow-up or the visit reminder falls due. Each row carries `waiting_since`, `waiting_state`, `waiting_due_at` and `waiting_subject_id` (the message, or handoff event, the wait is about — `POST /conversations/{id}/no-reply-needed` takes it as `subject_id`). Pair with `sort=wait` for the queue order. Conversations matching a workspace inbox view with «Sacar de Esperando respuesta» (`exclude_from_waiting`) are left out of `waiting=true` (and of the `waiting` count) unless the request names that view with `viewId`; they keep their `waiting_since`. An urgent (`handoff_urgent`) thread is never left out."
            },
            "required": false,
            "description": "true: only conversations where a customer is waiting on a human («Esperando respuesta»), team-wide. Waiting = open or pending, not filtered, on a channel you can reply in, and either an AI handoff nobody on the team has answered yet (a reply from ANY teammate clears it, including WhatsApp Business / Instagram app replies; a silent «Visita acordada» notice needs no answer) or a human-handled thread whose last message is the customer’s; waits older than 7 days are left out; and what the customer wrote still asks for a reply: a thread whose newest unanswered customer message was judged, with high confidence, as the customer closing the chat («ok», «gracias», 👍 after we answered) or owing nothing, a customer who paid (a transfer receipt after our bank details) or is at the store («llegué», «voy subiendo»), or a follow-up we promised for a later day that is not due yet, is left out (any other open promise of ours always stays); a customer who announced a store visit («mañana paso», «el martes voy») waits through that day and is left out once it is over with nothing newer from them (`visit_passed`: a «Recordatorio acordado» to check whether they came falls due the next morning). `closed`: exactly those left-out threads, still live (not resolved) — `waiting_state` says why (`closed_by_customer` | `nothing_owed` | `paid_or_in_store` | `promised_later` | `visit_passed`), `waiting_due_at` when a promised follow-up or the visit reminder falls due. Each row carries `waiting_since`, `waiting_state`, `waiting_due_at` and `waiting_subject_id` (the message, or handoff event, the wait is about — `POST /conversations/{id}/no-reply-needed` takes it as `subject_id`). Pair with `sort=wait` for the queue order. Conversations matching a workspace inbox view with «Sacar de Esperando respuesta» (`exclude_from_waiting`) are left out of `waiting=true` (and of the `waiting` count) unless the request names that view with `viewId`; they keep their `waiting_since`. An urgent (`handoff_urgent`) thread is never left out.",
            "name": "waiting",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "op": {
                    "type": "string",
                    "enum": [
                      "contains",
                      "equals"
                    ],
                    "default": "contains"
                  },
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "required": [
                  "key",
                  "value"
                ],
                "additionalProperties": false
              },
              "minItems": 1,
              "maxItems": 10,
              "description": "Conversation-attribute condition(s) — what the AI agent recorded with `add_metadata` or a person set (`conversation_attribute`). Format `<key>:<op>:<value>`, `op` = `contains` | `equals` (`<key>:<value>` reads as `contains`); repeat the parameter to AND several. Case- and accent-insensitive («18 pro máx» matches «18 Pro Max»), on the stored value as text (a string without its JSON quotes). Key `*` matches the value under ANY attribute key. Examples: `attribute=modelo_buscado:contains:18 pro max`, `attribute=*:contains:18 pro max`. Keys and their common values: `GET /inbox-views/attribute-keys`."
            },
            "required": false,
            "description": "Conversation-attribute condition(s) — what the AI agent recorded with `add_metadata` or a person set (`conversation_attribute`). Format `<key>:<op>:<value>`, `op` = `contains` | `equals` (`<key>:<value>` reads as `contains`); repeat the parameter to AND several. Case- and accent-insensitive («18 pro máx» matches «18 Pro Max»), on the stored value as text (a string without its JSON quotes). Key `*` matches the value under ANY attribute key. Examples: `attribute=modelo_buscado:contains:18 pro max`, `attribute=*:contains:18 pro max`. Keys and their common values: `GET /inbox-views/attribute-keys`.",
            "name": "attribute",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "minItems": 1,
              "maxItems": 20,
              "description": "Only conversations carrying ANY of these tags (`GET /tags` ids), comma-separated or repeated; at most 20."
            },
            "required": false,
            "description": "Only conversations carrying ANY of these tags (`GET /tags` ids), comma-separated or repeated; at most 20.",
            "name": "tagIds",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Apply a saved inbox view (`GET /inbox-views`): its filters are ANDed with every other parameter here, and its `waiting` (if any) applies when `waiting` is not given. A workspace view, or one of your own personal views; any other id is a 404. A view with «Sacar de Esperando respuesta» still lists its matches (with their `waiting_since`) — the exclusion only applies to `waiting=true` WITHOUT a `viewId`."
            },
            "required": false,
            "description": "Apply a saved inbox view (`GET /inbox-views`): its filters are ANDed with every other parameter here, and its `waiting` (if any) applies when `waiting` is not given. A workspace view, or one of your own personal views; any other id is a 404. A view with «Sacar de Esperando respuesta» still lists its matches (with their `waiting_since`) — the exclusion only applies to `waiting=true` WITHOUT a `viewId`.",
            "name": "viewId",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "updated_at",
                "last_message_date",
                "wait"
              ],
              "default": "created_at",
              "description": "`wait`: urgent (`handoff_urgent`) first, then by `waiting_since` — `order=desc` (default) is the longest wait first, `asc` the newest; conversations that are not waiting go last."
            },
            "required": false,
            "description": "`wait`: urgent (`handoff_urgent`) first, then by `waiting_since` — `order=desc` (default) is the longest wait first, `asc` the newest; conversations that are not waiting go last.",
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            },
            "required": false,
            "name": "order",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "counts"
              ]
            },
            "required": false,
            "name": "include",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "summary",
                "full"
              ],
              "default": "full",
              "description": "`full` (default): the inbox row with its `contact`, `ticket`, `lastMessage`, `tags`, `marketplaceWindow`, `ad_origin` and `last_call`, exactly as without the parameter. `summary`: only the identifying and funnel scalars — `id`, `display_id`, `channel`, `source`, `status`, `handler`, `assignee_user_id`, `contact_id`, `ticket_id`, `awaiting_human_since`, `first_ai_message_at`, `last_message_date`, `created_at`, `ad_origin`, plus `contact_name`, `ticket_status`, `current_stage_id` off the embeds and `assignee_name` / `unread` when the list computed them. Pagination, `meta` and `counts` are unchanged. Use `summary` to page the inbox cheaply, then `GET /conversations/{id}` for one thread in full."
            },
            "required": false,
            "description": "`full` (default): the inbox row with its `contact`, `ticket`, `lastMessage`, `tags`, `marketplaceWindow`, `ad_origin` and `last_call`, exactly as without the parameter. `summary`: only the identifying and funnel scalars — `id`, `display_id`, `channel`, `source`, `status`, `handler`, `assignee_user_id`, `contact_id`, `ticket_id`, `awaiting_human_since`, `first_ai_message_at`, `last_message_date`, `created_at`, `ad_origin`, plus `contact_name`, `ticket_status`, `current_stage_id` off the embeds and `assignee_name` / `unread` when the list computed them. Pagination, `meta` and `counts` are unchanged. Use `summary` to page the inbox cheaply, then `GET /conversations/{id}` for one thread in full.",
            "name": "fields",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversations",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "ad_origin": {
                            "$ref": "#/components/schemas/AdOrigin"
                          },
                          "campaign_origin": {
                            "$ref": "#/components/schemas/ConversationCampaignOrigin"
                          }
                        },
                        "required": [
                          "ad_origin"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 132,
                      "display_id": "C-132",
                      "channel": "web",
                      "external_id": "web:visitor-9f2c1a",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                      "snoozed_until": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "closed_at": null,
                      "close_deferred_until": null,
                      "transfer_immediately": false,
                      "filtered_at": null,
                      "filtered_reason": null,
                      "summary": null,
                      "summarized_at": null,
                      "history_summary": null,
                      "history_summary_upto_message_id": null,
                      "first_billable_at": null,
                      "billable": null,
                      "billing_reason": null,
                      "assigned_unanswered_alerted_at": null,
                      "assigned_unanswered_realerted_at": null,
                      "bot_gate_override_at": null,
                      "bot_gate_override_by": null,
                      "source": null,
                      "awaiting_human_since": null,
                      "awaiting_payment_since": null,
                      "awaiting_payment_escalated_at": null,
                      "awaiting_obligation_id": null,
                      "ad_referral": null,
                      "ad_join": null,
                      "ad_welcome": null,
                      "metadata": null,
                      "ai_control_source": "legacy",
                      "ai_control_changed_at": null,
                      "ai_control_revision": 0,
                      "ai_control_window_start": null,
                      "ai_control_window_end": null,
                      "ai_control_account_revision": null,
                      "ai_blocked_until": null,
                      "ai_keep_with_human": false,
                      "external_id_provisional": false,
                      "originated_at": null,
                      "originated_by_kind": null,
                      "originated_by_ai_agent_id": null,
                      "originated_by_user_id": null,
                      "originated_outbound_action_id": null,
                      "last_message_date": "2026-09-22T11:08:10.882Z",
                      "created_at": "2026-09-22T11:06:33.744Z",
                      "updated_at": "2026-09-22T11:08:10.882Z",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "name": "Rodrigo Pizarro",
                        "email": "rodrigo.pizarro@example.cl",
                        "phone": "+56977000021",
                        "external_id": "web:visitor-9f2c1a",
                        "avatar_url": null,
                        "city": null,
                        "country": null,
                        "language": "es",
                        "job_title": null,
                        "company_id": null,
                        "brand": null,
                        "social": {},
                        "social_stats": {},
                        "lifecycle_stage": "unknown",
                        "email_status": "subscribed",
                        "email_consent": false,
                        "outreach_consent_at": null,
                        "outreach_consent_source_url": null,
                        "outreach_consent_text_version": null,
                        "bot_replies_disabled_at": null,
                        "bot_replies_disabled_by": null,
                        "spam_at": null,
                        "blocked_at": null,
                        "archived_at": null,
                        "merged_at": null,
                        "merged_into_contact_id": null,
                        "search_text": "rodrigo pizarro rodrigo.pizarro@example.cl +56977000021",
                        "created_at": "2026-09-22T11:06:33.508Z",
                        "updated_at": "2026-09-22T11:06:33.508Z"
                      },
                      "ticket": {
                        "id": "dddddddd-0000-4000-8000-000000000001",
                        "display_id": "T-6",
                        "status": "open",
                        "handler": "human",
                        "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                        "current_stage_id": "55555555-0000-4000-8000-000000000011",
                        "snoozed_until": null
                      },
                      "lastMessage": {
                        "id": "eeeeeeee-0000-4000-8000-000000000002",
                        "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                        "sender_type": "api_key",
                        "created_at": "2026-09-22T11:08:10.882Z"
                      },
                      "marketplaceWindow": null,
                      "ad_origin": null,
                      "tags": [
                        {
                          "id": "12121212-0000-4000-8000-000000000001",
                          "name": "garantia",
                          "display_name": "Garantía"
                        }
                      ],
                      "last_call": null,
                      "handoff_urgent": false,
                      "waiting_since": null,
                      "waiting_state": null,
                      "waiting_due_at": null,
                      "waiting_subject_id": null
                    },
                    {
                      "id": "bbbbbbbb-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 133,
                      "display_id": "C-133",
                      "channel": "instagram",
                      "external_id": "<instagram-scoped-id>",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "ticket_id": null,
                      "status": "open",
                      "handler": "bot",
                      "assignee_user_id": null,
                      "assignee_assigned_at": null,
                      "snoozed_until": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "closed_at": null,
                      "close_deferred_until": null,
                      "transfer_immediately": false,
                      "filtered_at": null,
                      "filtered_reason": null,
                      "summary": null,
                      "summarized_at": null,
                      "history_summary": null,
                      "history_summary_upto_message_id": null,
                      "first_billable_at": null,
                      "billable": null,
                      "billing_reason": null,
                      "assigned_unanswered_alerted_at": null,
                      "assigned_unanswered_realerted_at": null,
                      "bot_gate_override_at": null,
                      "bot_gate_override_by": null,
                      "source": null,
                      "awaiting_human_since": null,
                      "awaiting_payment_since": null,
                      "awaiting_payment_escalated_at": null,
                      "awaiting_obligation_id": null,
                      "ad_referral": {
                        "ref": null,
                        "source": "ADS",
                        "type": "OPEN_THREAD",
                        "ad_id": "120236000000000001",
                        "ads_context_data": {
                          "ad_title": "Evaluación sin costo este mes",
                          "photo_url": null,
                          "video_url": "https://lookaside.fbsbx.com/ig_messaging_cdn/?asset_id=<asset>",
                          "post_id": "18000000000000001",
                          "product_id": null
                        }
                      },
                      "ad_join": null,
                      "ad_welcome": null,
                      "metadata": {
                        "ad_creative": {
                          "thumbnail_url": "https://media.vitrinadev.com/ad-creatives/a1a1a1a1-0000-4000-8000-000000000001/<digest>.jpg",
                          "source": "share_media",
                          "permalink": "https://www.instagram.com/p/<shortcode>/",
                          "archived_at": "2026-09-22T10:42:15.120Z"
                        }
                      },
                      "ai_control_source": "legacy",
                      "ai_control_changed_at": null,
                      "ai_control_revision": 0,
                      "ai_control_window_start": null,
                      "ai_control_window_end": null,
                      "ai_control_account_revision": null,
                      "ai_blocked_until": null,
                      "ai_keep_with_human": false,
                      "external_id_provisional": false,
                      "originated_at": null,
                      "originated_by_kind": null,
                      "originated_by_ai_agent_id": null,
                      "originated_by_user_id": null,
                      "originated_outbound_action_id": null,
                      "last_message_date": "2026-09-22T10:41:12.310Z",
                      "created_at": "2026-09-22T10:41:12.004Z",
                      "updated_at": "2026-09-22T10:41:12.310Z",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "name": "Rodrigo Pizarro",
                        "email": "rodrigo.pizarro@example.cl",
                        "phone": "+56977000021",
                        "external_id": "web:visitor-9f2c1a",
                        "avatar_url": null,
                        "city": null,
                        "country": null,
                        "language": "es",
                        "job_title": null,
                        "company_id": null,
                        "brand": null,
                        "social": {},
                        "social_stats": {},
                        "lifecycle_stage": "unknown",
                        "email_status": "subscribed",
                        "email_consent": false,
                        "outreach_consent_at": null,
                        "outreach_consent_source_url": null,
                        "outreach_consent_text_version": null,
                        "bot_replies_disabled_at": null,
                        "bot_replies_disabled_by": null,
                        "spam_at": null,
                        "blocked_at": null,
                        "archived_at": null,
                        "merged_at": null,
                        "merged_into_contact_id": null,
                        "search_text": "rodrigo pizarro rodrigo.pizarro@example.cl +56977000021",
                        "created_at": "2026-09-22T11:06:33.508Z",
                        "updated_at": "2026-09-22T11:06:33.508Z"
                      },
                      "ticket": null,
                      "lastMessage": {
                        "id": "eeeeeeee-0000-4000-8000-000000000001",
                        "content": "Hola, quiero más información",
                        "sender_type": "contact",
                        "created_at": "2026-09-22T10:41:12.310Z"
                      },
                      "marketplaceWindow": null,
                      "ad_origin": {
                        "platform": "instagram",
                        "kind": "ad",
                        "ad_external_id": "120236000000000001",
                        "ad_name": "Evaluación facial — septiembre",
                        "campaign_name": "Mensajes Instagram · Evaluación",
                        "title": "Evaluación sin costo este mes",
                        "thumbnail_url": "https://media.vitrinadev.com/ad-creatives/a1a1a1a1-0000-4000-8000-000000000001/<digest>.jpg",
                        "media_archived": true,
                        "video_url": "https://lookaside.fbsbx.com/ig_messaging_cdn/?asset_id=<asset>",
                        "source_url": "https://www.instagram.com/p/<shortcode>/",
                        "captured_at": "2026-09-22T10:41:12.004Z"
                      },
                      "tags": [],
                      "last_call": null,
                      "handoff_urgent": false,
                      "waiting_since": null,
                      "waiting_state": null,
                      "waiting_due_at": null,
                      "waiting_subject_id": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 18,
                      "limit": 20
                    },
                    "page": 1,
                    "totalPages": 1,
                    "hasNext": false,
                    "hasPrev": false
                  },
                  "counts": {
                    "myOpen": 3,
                    "myUnread": 1,
                    "unassigned": 2,
                    "myMentions": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Open a conversation from the platform side",
        "description": "The outbound counterpart to an inbound provider message — everything else in the inbox is created BY the provider webhook when a customer writes. Because nothing arrived from a provider, the caller supplies the channel and the contact identity.\n\nThe contact is RESOLVED before it is created, in this order: `contact.external_id`, then `contact.email`, then `contact.phone`. The first one that finds somebody wins and that contact is reused; only a miss on all three creates a new one. So calling this twice for the same person no longer leaves two contacts, and a 201 may well carry a contact that already existed — read `contact.id` rather than assuming the response describes a new record.\n\n⚠ A BLANK FIELD NEVER MATCHES. An email or phone you omit, send empty, or send in a shape we cannot parse is skipped entirely — it is not compared against contacts that have none, because fusing two unrelated people onto one record is worse than the duplicate it would avoid. Send only the identifiers you actually hold.\n\nMatching is deliberately tolerant of spelling: email is compared case-insensitively, and a phone is normalised first, so `+56 9 1234 5678`, `56912345678` and `912345678` all resolve to the same person. An identity recorded on the contact’s channels counts as much as the one stored on the contact itself, so a number captured during an earlier WhatsApp thread is found here too.\n\nCarry your own `contact.external_id` for anyone you may open a second thread with: it is the only key that cannot drift. An email address and a phone number can both be reassigned to a different person, which is exactly why they are consulted after it and not before.\n\n⚠ The thread is addressable only if the channel can work out where to send. On `web` the address IS the visitor session, so a conversation opened here gets a synthetic `manual:<uuid>` external id and sending on it answers 422 `no_channel_identity` — open a web thread from the widget instead. On `whatsapp` and `email` the contact's phone or address is the address, and the thread is sendable as soon as `messaging_account_id` names a connected account. A WhatsApp thread is keyed on the number (its wa_id), so when that number already has an open thread, that thread is returned instead of a second one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 50,
                    "default": "web"
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "messaging_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "contact": {
                    "type": "object",
                    "properties": {
                      "external_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 255
                      },
                      "name": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 255
                      },
                      "email": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 255,
                        "format": "email"
                      },
                      "phone": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 50
                      }
                    }
                  }
                }
              },
              "example": {
                "channel": "web",
                "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                "contact": {
                  "name": "Rodrigo Pizarro",
                  "email": "rodrigo.pizarro@example.cl",
                  "phone": "+56977000021"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 134,
                    "display_id": "C-134",
                    "channel": "web",
                    "external_id": "manual:3970dd58-bdce-4498-b8f7-e0acd4440d20",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": null,
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": null,
                    "assignee_assigned_at": null,
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": null,
                    "bot_gate_override_by": null,
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:07:43.585Z",
                    "created_at": "2026-09-22T11:07:43.585Z",
                    "updated_at": "2026-09-22T11:07:43.585Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/conversations/counts": {
      "get": {
        "summary": "Inbox badge counts (my open / unread / mentions / unassigned / waiting)",
        "description": "The four badge counts on their own, scoped to the calling principal: `myOpen` and `myUnread` are the conversations assigned to the member behind the credential, `myMentions` the ones that @-mention them, `unassigned` the whole workspace’s unowned queue. An API key has no member behind it, so its three \"my\" counts read 0 and only `unassigned` is meaningful — use a personal token to get a person’s badges.\n\n«Esperando respuesta» (the waiting queue): `waiting` is how many conversations have a customer waiting on a human, team-wide (the list `GET /conversations?waiting=true` returns exactly these); `waitingUrgent` how many of them are flagged urgent; `waitingMine` how many are assigned to the caller (0 for an API key); `waitingOldestSince` when the longest wait started (ISO, `null` when nobody waits) — render it as «el más antiguo 2 h»; `waitingClosed` how many live threads were left out because the customer closed the chat, nothing is owed, they paid or are in the store, or a follow-up we promised is not due yet (the list `waiting=closed`); `waitingExcluded` how many waiting conversations a workspace view with «Sacar de Esperando respuesta» (`exclude_from_waiting`) took out of `waiting` — they are not in the queue, the banner or the urgent / mine / oldest figures, and they wait in their view (an urgent `handoff_urgent` thread is never taken out) (`GET /inbox-views` gives each view its `waiting_count`).\n\n`GET /conversations?include=counts` returns the same payload beside a page of conversations; this route exists for the caller that wants the badges without one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "responses": {
          "200": {
            "description": "Inbox counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "myOpen": 3,
                    "myUnread": 1,
                    "unassigned": 2,
                    "myMentions": 0,
                    "waiting": 3,
                    "waitingUrgent": 1,
                    "waitingMine": 1,
                    "waitingOldestSince": "2026-10-31T13:05:00.000Z",
                    "waitingClosed": 4,
                    "waitingExcluded": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}": {
      "get": {
        "summary": "Get conversation",
        "description": "The conversation row with the full transcript embedded (`message[]`, unbounded) — the historical, still-default shape every existing consumer keeps seeing unchanged.\n\nPass `?exclude=messages` to opt into the SAME row without that embed. Any caller that re-reads this on a clock should: the embed has no limit, so a two-year-old thread returns two years of messages every time. `GET /conversations/{id}/messages` pages them properly.\n\nAlso carries `ad_origin_nudge: 'connect_meta_ads' | null`, non-null ONLY when the caller holds `integrations:write`, the conversation came from a Meta ad (click-to-WhatsApp or an Instagram DM ad), and the workspace has no connected `meta_ads` integration. THIS ROUTE ONLY — deliberately absent from the list, where the predicate would be an N+1.\n\nAnd `handoff_resolved_suggested: boolean` (#2968) — true only on an open/pending AI-handoff thread whose customer is still waiting on us AND Jev already read their own pending words as saying the matter is resolved (checked once per customer message, at the next handoff re-notify tick). The inbox reads this to offer a one-click close. THIS ROUTE ONLY, same N+1 reasoning as `ad_origin_nudge` — unlike `handoff_urgent` below, this one was never worth a column since only the open thread needs it.\n\n`handoff_urgent: boolean` (#2969) is a plain `conversation` column, not a per-route annotation: true while an open/pending AI-handoff thread's customer is still waiting on us and Jev read their newest pending message as urgent (a clinic post-treatment complication, or an explicit same-day deadline) — kept in sync by the same classification that also tightens the re-notify cadence, and present on every conversation read, list included, because the inbox shows the flag on the conversation ROW too, not only its header.\n\nCarries `ad_origin` exactly as `GET /conversations` rows do.\n\n`reply_via` (THIS ROUTE ONLY): non-null on a conversation that cannot carry a reply (a website form or a Yapo lead) — the WhatsApp thread to answer in, or `conversation_id: null` when the contact has none open yet.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "messages"
              ]
            },
            "required": false,
            "name": "exclude",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ad_origin": {
                          "$ref": "#/components/schemas/AdOrigin"
                        },
                        "campaign_origin": {
                          "$ref": "#/components/schemas/ConversationCampaignOrigin"
                        },
                        "reply_via": {
                          "$ref": "#/components/schemas/ConversationReplyVia"
                        }
                      },
                      "required": [
                        "ad_origin"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 132,
                    "display_id": "C-132",
                    "channel": "web",
                    "external_id": "web:visitor-9f2c1a",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": null,
                    "bot_gate_override_by": null,
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:08:10.882Z",
                    "created_at": "2026-09-22T11:06:33.744Z",
                    "updated_at": "2026-09-22T11:08:10.882Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Rodrigo Pizarro",
                      "email": "rodrigo.pizarro@example.cl",
                      "phone": "+56977000021",
                      "external_id": "web:visitor-9f2c1a",
                      "avatar_url": null,
                      "city": null,
                      "country": null,
                      "language": "es",
                      "job_title": null,
                      "company_id": null,
                      "brand": null,
                      "social": {},
                      "social_stats": {},
                      "lifecycle_stage": "unknown",
                      "email_status": "subscribed",
                      "email_consent": false,
                      "outreach_consent_at": null,
                      "outreach_consent_source_url": null,
                      "outreach_consent_text_version": null,
                      "bot_replies_disabled_at": null,
                      "bot_replies_disabled_by": null,
                      "spam_at": null,
                      "blocked_at": null,
                      "archived_at": null,
                      "merged_at": null,
                      "merged_into_contact_id": null,
                      "search_text": "rodrigo pizarro rodrigo.pizarro@example.cl +56977000021",
                      "created_at": "2026-09-22T11:06:33.508Z",
                      "updated_at": "2026-09-22T11:06:33.508Z"
                    },
                    "ticket": {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "display_id": "T-6",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "snoozed_until": null
                    },
                    "lastMessage": {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "sender_type": "api_key",
                      "created_at": "2026-09-22T11:08:10.882Z"
                    },
                    "marketplaceWindow": null,
                    "ad_origin": null,
                    "tags": [
                      {
                        "id": "12121212-0000-4000-8000-000000000001",
                        "name": "garantia",
                        "display_name": "Garantía"
                      }
                    ],
                    "last_call": null,
                    "handoff_urgent": false,
                    "waiting_since": null,
                    "waiting_state": null,
                    "waiting_due_at": null,
                    "waiting_subject_id": null,
                    "ad_origin_nudge": null,
                    "handoff_resolved_suggested": false,
                    "message": [
                      {
                        "id": "eeeeeeee-0000-4000-8000-000000000001",
                        "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                        "content": "¿Me pueden confirmar la hora del jueves?",
                        "type": "text",
                        "sender_role": "user",
                        "sender_type": "contact",
                        "sender_id": null,
                        "created_at": "2026-09-22T11:07:02.824Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/ai-status": {
      "get": {
        "summary": "Why the AI is silent on this conversation",
        "description": "A machine-readable `silence_reason` (plus a human `explanation`), or `null` when the AI *would* answer the next inbound message. Separate from `GET /conversations/{id}` on purpose: resolving the reason walks the agent precedence chain and can cost four queries, which the inbox's hottest path must not pay on every thread open — least of all to learn that there is nothing to report.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ silence_reason, explanation, … }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "silence_reason": "human_took_over",
                    "explanation": "A teammate took control. The AI stays silent through the protected window, or until an explicit return when that window is unbounded.",
                    "bot_gate_override_at": null,
                    "awaiting_payment": null,
                    "gate_detail": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/participants": {
      "get": {
        "summary": "Teammates who have been on this conversation",
        "description": "The current assignee **first**, then every teammate who has written in the thread, each as `{ user_id, name, avatar_url, is_assignee }`.\n\nThe union of the two is deliberate: the writers alone miss an assignee who was handed the thread and has not replied yet, and the assignee alone misses the colleague who covered it last week. Only `human_user` messages count — an AI reply’s `sender_id` is an agent, and an API key's is the key, neither of which is a teammate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Participants, assignee first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "name": "Camila Rojas",
                      "avatar_url": null,
                      "is_assignee": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/linked-records": {
      "get": {
        "summary": "The ticket and open leads this conversation's work lives on",
        "description": "The ticket the thread hangs off and every open lead it feeds, each annotated with its board and the columns the card may move to next (`allowed_transitions_to`) — so a caller can offer the legal moves without reading the pipeline separately. `ticket` is `null` on a thread that never needed one; `leads` is `[]` when nothing commercial came of it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Linked ticket/leads with pipeline + stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "ticket": {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "display_id": "T-6",
                      "status": "open",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011",
                      "pipeline": {
                        "id": "44444444-0000-4000-8000-000000000001",
                        "name": "Soporte",
                        "stage": [
                          {
                            "id": "55555555-0000-4000-8000-000000000011",
                            "name": "Nuevo",
                            "slug": "new",
                            "position": 0,
                            "category": "open",
                            "allowed_transitions_to": [
                              "55555555-0000-4000-8000-000000000012"
                            ]
                          }
                        ]
                      }
                    },
                    "leads": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/export": {
      "get": {
        "summary": "Export the whole conversation (json | markdown)",
        "description": "One bundle with the conversation, its contact, every message, the internal notes and the attributes — what a person would attach to a complaint or hand to a lawyer. `?format=markdown` returns the same bundle rendered as a readable transcript instead of JSON.\n\nUnbounded by design: this is the export, not a page. For an ongoing read use `GET /conversations/{id}/messages`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "markdown"
              ],
              "default": "json"
            },
            "required": false,
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The bundle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation": {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "display_id": "C-132",
                      "channel": "web",
                      "status": "open",
                      "created_at": "2026-09-22T11:06:33.744Z"
                    },
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "Rodrigo Pizarro",
                      "email": "rodrigo.pizarro@example.cl",
                      "phone": "+56977000021"
                    },
                    "messages": [
                      {
                        "id": "eeeeeeee-0000-4000-8000-000000000001",
                        "sender_type": "contact",
                        "content": "¿Me pueden confirmar la hora del jueves?",
                        "created_at": "2026-09-22T11:07:02.824Z"
                      },
                      {
                        "id": "eeeeeeee-0000-4000-8000-000000000002",
                        "sender_type": "api_key",
                        "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                        "created_at": "2026-09-22T11:08:10.882Z"
                      }
                    ],
                    "notes": [],
                    "attributes": [
                      {
                        "key": "numero_de_caso",
                        "value": "F-4821"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/messages": {
      "get": {
        "summary": "Paginated message playback",
        "description": "Rows are the `message` table verbatim, plus the `author` every message carries (ADR 0106 §3.1): the contact, a member, a member through a connected app or a personal token (`author.via`), the AI agent, an API key — whose `name` is the name it had WHEN IT WROTE, so renaming or revoking the key never rewrites history — or the system.\n\nOutbound delivery is carried by four fields the inbox renders together: `delivery_status` (`sent` → `delivered` → `read`, `failed`, or `retrying` — the broker refused the send and another attempt is scheduled), `next_attempt_at` (when that attempt fires; set only while `retrying`), `delivery_attempt` (send attempts made — 1 is the original send) and `delivery_error` (the provider's reason on `failed`). A `retrying` message has NOT failed: it only becomes `failed` once the retry ladder (1 min, 5 min, 15 min, 1 h, 3 h) is exhausted. All four are null/0 on inbound rows.\n\nPaged by cursor (`meta.pagination.nextCursor`), oldest first by default. A `sender_type` of `system` is a thread event — an assignment, a status change — not a message anyone sent.\n\n`attachments` has one entry per `media_urls` entry, in the same order (`index`, `url`, `mime`, `playback_url`, `playback_mime`). On audio, `playback_url` is a file every iOS player handles — the original when it already is mp4/m4a/mp3/aac, else its AAC rendition (`audio/mp4`); null until that rendition exists, in which case `GET /messages/{id}/audio-playback?attachment={index}` generates it and redirects.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "required": false,
            "name": "order",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Messages, each with its `author`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "¿Me pueden confirmar la hora del jueves?",
                      "type": "text",
                      "sender_role": "user",
                      "sender_type": "contact",
                      "sender_id": null,
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": null,
                      "delivery_error": null,
                      "delivery_attempt": 0,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "contact",
                        "id": null,
                        "name": null
                      },
                      "created_at": "2026-09-22T11:07:02.824Z",
                      "updated_at": "2026-09-22T11:07:02.824Z",
                      "attachments": []
                    },
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000009",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "",
                      "type": "audio",
                      "sender_role": "user",
                      "sender_type": "contact",
                      "sender_id": null,
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": [
                        "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg"
                      ],
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": {
                        "mime": "audio/ogg; codecs=opus",
                        "size": 18342
                      },
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": null,
                      "delivery_error": null,
                      "delivery_attempt": 0,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "contact",
                        "id": null,
                        "name": null
                      },
                      "created_at": "2026-09-22T11:07:40.112Z",
                      "updated_at": "2026-09-22T11:07:40.112Z",
                      "attachments": [
                        {
                          "index": 0,
                          "url": "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg",
                          "mime": "audio/ogg; codecs=opus",
                          "playback_url": "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg.m4a",
                          "playback_mime": "audio/mp4"
                        }
                      ]
                    },
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "type": "text",
                      "sender_role": "assistant",
                      "sender_type": "api_key",
                      "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": "sent",
                      "delivery_error": null,
                      "delivery_attempt": 1,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "api_key",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM propio"
                      },
                      "created_at": "2026-09-22T11:08:10.882Z",
                      "updated_at": "2026-09-22T11:08:10.882Z",
                      "attachments": []
                    },
                    {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "type": "text",
                      "sender_role": "assistant",
                      "sender_type": "human_user",
                      "sender_id": "11111111-0000-4000-8000-000000000001",
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": "sent",
                      "delivery_error": null,
                      "delivery_attempt": 1,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "member",
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Camila Rojas",
                        "via": {
                          "kind": "connected_app",
                          "name": "Claude"
                        }
                      },
                      "created_at": "2026-09-22T11:08:10.882Z",
                      "updated_at": "2026-09-22T11:08:10.882Z",
                      "attachments": []
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 50,
                      "nextCursor": "eyJ0cyI6IjIwMjYtMDktMjJUMTE6MDg6MTAuODgyWiIsImlkIjoiZWVlZWVlZWUtMDAwMC00MDAwLTgwMDAtMDAwMDAwMDAwMDAyIn0="
                    },
                    "order": "asc"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Send a message to the customer",
        "description": "The plain-text reply. The message is persisted with the caller as its `author` and shipped through whatever provider the conversation is on.\n\n`cc`, `bcc` and `html` are EMAIL ONLY and a 400 anywhere else; `html` is sanitised server-side and sent as the `text/html` part, with `content` kept as the plain-text fallback. `signature_id` picks one of the sending member's saved signatures — omit it for their default, send `null` for none.\n\n`send_at` (email only, ≥1 min ahead, ≤30 days) schedules the reply instead of sending it: the answer is `{ scheduled: true, id, send_at, status }`, not a message. The política de envíos judges it **twice** — now, and again when it fires, because a contact can opt out or a hold can land in between; a Bloqueo at fire time stops it and the scheduled row ends `failed` with the reason. Cancel with `DELETE /conversations/{id}/scheduled-messages/{scheduledId}`.\n\n**Instagram and Messenger reply window.** A reply from a signed-in member may go out up to 7 days after the customer's last message: past the standard 24 hours it is sent with Meta's `HUMAN_AGENT` tag. Past 7 days — or when the customer never wrote — it is refused with 422 `MESSAGING_WINDOW_CLOSED` and nothing is stored. Every other caller (API key, personal token, connected app) keeps the 24-hour window.\n\n**Read-only channels.** A website form (`website`) or Yapo lead conversation has no reply rail: the send is refused with 409 `CHANNEL_READ_ONLY` (`details.channel`) and nothing is stored or queued. The same holds for templates, attachments, location, Flows and voice notes. Answer on WhatsApp — see `reply_via` on `GET /conversations/{id}`.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "text"
                    ],
                    "default": "text"
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    },
                    "maxItems": 20
                  },
                  "bcc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    },
                    "maxItems": 20
                  },
                  "signature_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "send_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                },
                "required": [
                  "content"
                ]
              },
              "example": {
                "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                "acknowledge": [
                  "conversation_human_owned"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/email": {
      "post": {
        "summary": "A person starts an email — «Escribir por correo» / «Nuevo correo»",
        "description": "Creates AND sends a new email thread in one operation (ADR 0108 §Decision 10): a Contact id (must already carry an email) or a bare address (an unknown one becomes a Contact — the same de-duplication an inbound sender goes through), subject, body, optional cc, and the signature the reply composer already resolves. It originates through the tenant's connected mailbox (`messaging-accounts/email-sender`), keys the new conversation on the provider's thread, and the conversation is born under HUMAN control, assigned to the caller — never the AI. Unlike every automated rail, this send never consumes or waits on the mailbox budget and carries no unsubscribe footer: it is correspondence, not a rail. Refused 422 `EMAIL_SENDER_UNRESOLVED` when no sending mailbox resolves — none enabled, several with none designated, or the one that would send being disconnected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "to": {
                    "type": "string",
                    "maxLength": 255,
                    "format": "email"
                  },
                  "subject": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "email"
                    },
                    "maxItems": 20
                  },
                  "html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "signature_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "from_account_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "subject",
                  "content"
                ]
              },
              "example": {
                "contact_id": "22222222-0000-4000-8000-000000000001",
                "subject": "Resumen de lo que conversamos hoy",
                "content": "Hola María, te dejo por escrito el resumen de lo que revisamos hoy.",
                "cc": [
                  "operaciones@example.cl"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The originated conversation, its first message, and the resolved recipient Contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "conversation": {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 132,
                      "display_id": "C-132",
                      "channel": "email",
                      "external_id": "web:visitor-9f2c1a",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                      "snoozed_until": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "closed_at": null,
                      "close_deferred_until": null,
                      "transfer_immediately": false,
                      "filtered_at": null,
                      "filtered_reason": null,
                      "summary": null,
                      "summarized_at": null,
                      "history_summary": null,
                      "history_summary_upto_message_id": null,
                      "first_billable_at": null,
                      "billable": null,
                      "billing_reason": null,
                      "assigned_unanswered_alerted_at": null,
                      "assigned_unanswered_realerted_at": null,
                      "bot_gate_override_at": null,
                      "bot_gate_override_by": null,
                      "source": null,
                      "awaiting_human_since": null,
                      "awaiting_payment_since": null,
                      "awaiting_payment_escalated_at": null,
                      "awaiting_obligation_id": null,
                      "ad_referral": null,
                      "ad_join": null,
                      "ad_welcome": null,
                      "metadata": null,
                      "ai_control_source": "legacy",
                      "ai_control_changed_at": null,
                      "ai_control_revision": 0,
                      "ai_control_window_start": null,
                      "ai_control_window_end": null,
                      "ai_control_account_revision": null,
                      "ai_blocked_until": null,
                      "ai_keep_with_human": false,
                      "external_id_provisional": false,
                      "originated_at": null,
                      "originated_by_kind": null,
                      "originated_by_ai_agent_id": null,
                      "originated_by_user_id": null,
                      "originated_outbound_action_id": null,
                      "last_message_date": "2026-09-22T11:08:10.882Z",
                      "created_at": "2026-09-22T11:06:33.744Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "message": {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "type": "text",
                      "sender_role": "assistant",
                      "sender_type": "api_key",
                      "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": "sent",
                      "delivery_error": null,
                      "delivery_attempt": 1,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "api_key",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM propio"
                      },
                      "created_at": "2026-09-22T11:08:10.882Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "created": false,
                      "email": "maria.gonzalez@example.cl"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/conversations/whatsapp": {
      "post": {
        "summary": "A person opens a WhatsApp thread — «Escribir» → WhatsApp",
        "description": "Opens the WhatsApp conversation of a Contact that carries a phone, without sending anything: 201 when a new thread was created, 200 when an active one already existed. The thread is keyed on the phone number so the provider can deliver, and is born under HUMAN control, assigned to the caller. The composer then applies the 24-hour window and template rules. 422 when the contact has no phone or the workspace has no WhatsApp channel connected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "contact_id"
                ]
              },
              "example": {
                "contact_id": "22222222-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "An active WhatsApp thread already existed (`created: false`)"
          },
          "201": {
            "description": "The created WhatsApp conversation and `created: true`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "conversation": {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 132,
                      "display_id": "C-132",
                      "channel": "whatsapp",
                      "external_id": "web:visitor-9f2c1a",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                      "snoozed_until": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "closed_at": null,
                      "close_deferred_until": null,
                      "transfer_immediately": false,
                      "filtered_at": null,
                      "filtered_reason": null,
                      "summary": null,
                      "summarized_at": null,
                      "history_summary": null,
                      "history_summary_upto_message_id": null,
                      "first_billable_at": null,
                      "billable": null,
                      "billing_reason": null,
                      "assigned_unanswered_alerted_at": null,
                      "assigned_unanswered_realerted_at": null,
                      "bot_gate_override_at": null,
                      "bot_gate_override_by": null,
                      "source": null,
                      "awaiting_human_since": null,
                      "awaiting_payment_since": null,
                      "awaiting_payment_escalated_at": null,
                      "awaiting_obligation_id": null,
                      "ad_referral": null,
                      "ad_join": null,
                      "ad_welcome": null,
                      "metadata": null,
                      "ai_control_source": "legacy",
                      "ai_control_changed_at": null,
                      "ai_control_revision": 0,
                      "ai_control_window_start": null,
                      "ai_control_window_end": null,
                      "ai_control_account_revision": null,
                      "ai_blocked_until": null,
                      "ai_keep_with_human": false,
                      "external_id_provisional": false,
                      "originated_at": null,
                      "originated_by_kind": null,
                      "originated_by_ai_agent_id": null,
                      "originated_by_user_id": null,
                      "originated_outbound_action_id": null,
                      "last_message_date": "2026-09-22T11:08:10.882Z",
                      "created_at": "2026-09-22T11:06:33.744Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "created": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/conversations/{id}/scheduled-messages": {
      "get": {
        "summary": "List pending scheduled sends",
        "description": "The replies waiting to fire on this conversation, plus the ones that FAILED in the last 7 days (`status: failed`), one per send. A failed row carries `failure: { code, label }`: `code` is one of `copy_refused`, `policy_blocked`, `conversation_missing`, `delivery_failed` or `not_sent`, and `label` is a Spanish sentence to show as is; the raw error is never exposed. `failure` is null on a pending row. One that has gone out, was cancelled or was dismissed leaves this list. `policy_acknowledged` records the Advertencias the author went ahead over when they scheduled it; those are not re-asked at fire time, a Bloqueo still is.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled messages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "15151515-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "sender_user_id": "11111111-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te recuerdo la visita de mañana a las 10:00.",
                      "html": null,
                      "cc": [],
                      "bcc": [],
                      "send_at": "2026-09-23T13:00:00+00:00",
                      "status": "scheduled",
                      "failure": null,
                      "created_at": "2026-09-22T11:09:04.112Z"
                    },
                    {
                      "id": "44444444-0000-4000-8000-00000000b002",
                      "content": "Adjunto la cotización que me pediste.",
                      "send_at": "2026-09-22T15:00:00+00:00",
                      "status": "failed",
                      "failure": {
                        "code": "copy_refused",
                        "label": "Una dirección en copia ya no puede recibir correos (se dio de baja, rebotó o marcó spam). Quítala de cc/bcc y vuelve a enviarlo."
                      },
                      "created_at": "2026-09-22T10:02:11.004Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/scheduled-messages/{scheduledId}": {
      "delete": {
        "summary": "Cancel a scheduled send",
        "description": "The reply never goes out and the row is gone — there is no un-cancel, rescheduling means composing it again. On a FAILED row it dismisses it: the row leaves the list. A message that already went out (or was already cancelled or dismissed) is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "scheduledId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/conversations/{id}/templates": {
      "post": {
        "summary": "Send an approved WhatsApp template",
        "description": "The only way to reach a customer once WhatsApp’s 24-hour customer-care window has closed — free-form text is rejected by Meta outside it. `params` fills each `{{name}}` in the template body. Rejects with 400 on a non-WhatsApp conversation. The rendered result is persisted as an ordinary message, so the thread shows exactly what was delivered.\n\nThe template CATEGORY decides the legal basis, not the caller: a MARKETING template is judged as marketing whatever it is called, which is why a contact who never consented to marketing is a 422 here and not on a UTILITY template.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "template_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 2000
                    }
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  },
                  "keep_handler": {
                    "type": "boolean",
                    "description": "Send without taking the conversation over: handler and assignee stay as they are, so the AI keeps answering the reply. For proactive outreach sent on the AI's behalf (a check-in, a recontacto). Default false: a person sending a template takes the thread, like any other reply."
                  }
                },
                "required": [
                  "template_id"
                ]
              },
              "example": {
                "template_id": "c3c3c3c3-0000-4000-8000-000000000001",
                "params": {
                  "nombre": "Rodrigo",
                  "hora": "10:00"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/flows": {
      "post": {
        "summary": "Send a published WhatsApp Flow",
        "description": "Sends an interactive form (a Meta **Flow**) into the conversation, referenced by `flow_id` and dressed with `body`/`cta` and optional `header`/`footer`.\n\n**Unlike a template this does NOT re-open a closed 24h window** — it is an ordinary interactive message, so it only lands while the window is open; with the window closed the send is refused with **409** `window_closed` (send a template instead). WhatsApp conversations only; anything else is a 400.\n\n`header` is a union: a string is a TEXT header, an object `{ type, media_id }` is a MEDIA header whose `media_id` comes from `POST /conversations/{id}/flow-media`.\n\nThe Flow itself lives on Meta; this endpoint only references it. The customer's completed form returns asynchronously as a `flow_reply` contact message, not in this response.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "flow_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "cta": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 30
                  },
                  "header": {
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 60
                      },
                      {
                        "type": "object",
                        "properties": {
                          "type": {
                            "type": "string",
                            "enum": [
                              "image",
                              "video",
                              "document"
                            ]
                          },
                          "media_id": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 128
                          }
                        },
                        "required": [
                          "type",
                          "media_id"
                        ]
                      }
                    ]
                  },
                  "footer": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "context": {
                    "type": "object",
                    "properties": {
                      "subject": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "location": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "appointment_type": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "staff": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "service": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      }
                    },
                    "additionalProperties": false
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                },
                "required": [
                  "flow_id",
                  "body",
                  "cta"
                ]
              },
              "example": {
                "flow_id": "1234567890123456",
                "body": "Completa estos datos y agendamos tu hora.",
                "cta": "Completar"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted flow message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/flow-media": {
      "post": {
        "summary": "Mint a media_id for a Flow header (multipart/form-data)",
        "description": "Pre-uploads the multipart `file` to WhatsApp and answers the `{ media_id, header_type }` you then pass as the `header` of `POST /conversations/{id}/flows`.\n\nA separate step rather than multipart on the send route so a failed upload never sends a half-formed message. **Nothing is persisted on our side** — the `media_id` lives on Meta and expires there, and no message leaves Vitrina, which is why this one does not take `messages:send`.\n\nInteractive headers take image, video or document; **audio is a 400**, as is a non-WhatsApp conversation or a channel whose provider cannot pre-upload media.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "@nota-de-voz.m4a"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ media_id, header_type }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "media_id": "2038475610394856",
                    "header_type": "image"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/location": {
      "post": {
        "summary": "Send a location pin to the customer",
        "description": "WhatsApp only. Exactly ONE source: `location_id` (a branch of the workspace), `maps_url` (a pasted Google Maps link — short `maps.app.goo.gl` links are expanded server-side), or `latitude` + `longitude`. Two sources, or a lone `latitude`, is a 400.\n\nFor a branch the coordinates come from its `geo`, falling back to expanding its `arrival_info.maps_url` (cached back onto `geo` on the first send); a branch with neither is a 400. `name` / `address` are what the customer reads on the card — sent verbatim when given, otherwise taken from the branch.\n\nThe persisted message is `type: \"location\"` with the pin on `metadata.location`; its `content` is the same text the card shows.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "location_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "maps_url": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2048
                  },
                  "latitude": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90
                  },
                  "longitude": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "address": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                }
              },
              "example": {
                "location_id": "b1b1b1b1-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted location message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Sucursal Providencia — Av. Nueva Providencia 2214, Providencia",
                    "type": "location",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": {
                      "location": {
                        "latitude": -33.4262,
                        "longitude": -70.6162,
                        "name": "Sucursal Providencia",
                        "address": "Av. Nueva Providencia 2214, Providencia"
                      }
                    },
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/attachments": {
      "post": {
        "summary": "Send a file to the customer (multipart/form-data)",
        "description": "Stores the bytes, ships them through the channel provider, then persists the message. Provider send happens **before** persistence on purpose: a delivery failure surfaces as an error instead of leaving a \"sent\" row for a message the customer never got. Optional `caption`.\n\n`acknowledge` is a COMMA-SEPARATED string in this multipart form, not a JSON array.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension. In the JSON body `acknowledge` is the same comma-separated string.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "caption": {
                    "type": "string"
                  },
                  "acknowledge": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "file": "@comprobante.pdf",
                "caption": "Te adjunto el comprobante.",
                "acknowledge": "conversation_human_owned"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "caption": {
                    "type": "string"
                  },
                  "acknowledge": {
                    "type": "string"
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "cotizacion.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64",
                "caption": "Te envío la cotización"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted attachment message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Te adjunto el comprobante.",
                    "type": "file",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": [
                      "https://media.vitrinadev.com/conversations/.../boleta.pdf"
                    ],
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/voice": {
      "post": {
        "summary": "Send a voice note (multipart/form-data)",
        "description": "Transcodes the upload to OGG/Opus — the format WhatsApp renders as a push-to-talk bubble rather than a file — and sends it as audio. Only channels whose capabilities include audio accept this (WhatsApp today); anywhere else is a 400. `acknowledge` is a COMMA-SEPARATED string in this multipart form.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "acknowledge": {
                    "type": "string"
                  }
                }
              },
              "example": {
                "file": "@nota-de-voz.m4a",
                "acknowledge": "conversation_human_owned"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted audio message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/voice-clips/{clipId}/send": {
      "post": {
        "summary": "Send a library voice clip to the customer",
        "description": "**Deprecated** — use `POST /conversations/{id}/media-assets/{assetId}/send` (a voice clip is an audio file of the library, same id). Kept working unchanged until it is removed.\n\nSends one clip of the workspace «Clip de voz» library (`GET /voice-clips`) into the conversation as a voice note, as the caller: OGG/Opus with the push-to-talk flag on WhatsApp, AAC by public link on Instagram. The clip's transcript is stored on the message row. Everything `POST /conversations/{id}/voice` checks applies — the reply rail, the política de envíos, the Instagram/Messenger 7-day HUMAN_AGENT window and the provider's own refusal.\n\nRefused with 409 `CONFLICT` and `details.code`: `VOICE_CLIP_EXPIRED` (the clip is past `expires_at`; nothing is sent) or `VOICE_NOTE_UNSUPPORTED` (the channel cannot carry audio — email, web chat, Messenger, Marketplace; `details.text` holds the clip's words, or null, so the caller can offer them as a text reply; nothing is sent). Another workspace's clip is 404.\n\nNeeds `macros:read` (the library) as well as `conversations:write` and `messages:send`.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "clipId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                },
                "default": {}
              },
              "example": {}
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted audio message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/media-assets/{assetId}/send": {
      "post": {
        "summary": "Send a library file to the customer",
        "description": "Sends one file of the workspace library (`GET /media-assets`) into the conversation as its own message, as the caller. Images, videos and PDFs are copied into the conversation (the thread keeps its copy if the library file is later replaced or deleted) and go out with `caption` (omitted = the file’s own caption; null = none). Audio goes out as a voice note with its transcript, exactly like `POST /conversations/{id}/voice`. The message carries `metadata.media_asset_id`, and the send is counted on the file (`send_count`). A file already sent in this conversation may be sent again.\n\nRefused with `details.code`: 409 `MEDIA_ASSET_NOT_SENDABLE` (`details.reason`: `expired`, or `consent_required` — it shows a person whose consent is not confirmed), 409 `MEDIA_CHANNEL_UNSUPPORTED` (`details.channel`, `details.kind` — e.g. a PDF on Instagram), 409 `VOICE_NOTE_UNSUPPORTED` (audio on a channel without voice notes; `details.text` holds its words so the caller can send them as text), 413 `MEDIA_TOO_LARGE` (over the channel’s per-file cap — email takes 3 MB). Nothing is sent on any refusal. Another workspace’s file is 404.\n\nNeeds `macros:read` (the library) as well as `conversations:write` and `messages:send`.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "assetId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "caption": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024,
                    "description": "Text sent with the file. Omitted = the file’s own caption; null = none. Ignored for audio."
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over — every code from a 409 `OUTBOUND_WARNING` response's `reasons`, e.g. `[\"quiet_hours\"]`. Recorded in the audit log and the outbound ledger. Has no effect on a Bloqueo (422 `OUTBOUND_BLOCKED`)."
                  }
                },
                "default": {}
              },
              "example": {
                "caption": "Resultado real tras 3 sesiones"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The persisted media message",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "eeeeeeee-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                    "type": "text",
                    "sender_role": "assistant",
                    "sender_type": "api_key",
                    "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                    "correlation_id": null,
                    "external_message_id": null,
                    "media_urls": null,
                    "media_sensitivity": null,
                    "media_processing_state": null,
                    "media_expires_at": null,
                    "derived_context_expires_at": null,
                    "metadata": null,
                    "tool_calls": null,
                    "tool_call_id": null,
                    "delivery_status": "sent",
                    "delivery_error": null,
                    "delivery_attempt": 1,
                    "next_attempt_at": null,
                    "delivered_at": null,
                    "read_at": null,
                    "author": {
                      "kind": "api_key",
                      "id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "name": "CRM propio"
                    },
                    "created_at": "2026-09-22T11:08:10.882Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/voice-note": {
      "post": {
        "summary": "Send a typed line as a voice note in the agent’s voice",
        "description": "The teammate types; Vitrina speaks it in the AI agent’s configured voice (`behavior_policies.voice_note`, published setting) so the conversation keeps one voice whoever answers. Same pipeline as the agent’s own notes: numbers, prices and times are spoken as words, the Whisper check must hear every number and model, and the characters count against the workspace’s daily cap (`GET /ai-agents/{id}/voice-note/usage`). Sent as a WhatsApp voice note (OGG/Opus) or an Instagram audio (M4A), stored as an `audio` message authored by the caller, with the typed text as its transcript (`metadata.attachment_context`; no speech-to-text).\n\nWhose voice: `ai_agent_id`, else the agent that answers this conversation (origin agent, then the channel’s agent, then the workspace default).\n\n**A refusal is 200 with `ok: false`** and nothing is sent: `code` is one of `not_configured` (voice notes off or no voice chosen), `invalid_text` (`reason`: empty | too_long | phone_or_rut), `cap_reached`, `check_failed` (with `check`; characters were spent), `provider_error`, `channel_unsupported` (only WhatsApp and Instagram carry voice notes), `agent_not_found`, `transcode_failed`, `too_large`, `no_public_url`. Send the text instead. A channel rejection is the provider error, as on `POST /conversations/{id}/voice`.\n\nRequires `messages:send` in addition to `conversations:write`. Sent by an API key, a personal token or a connected app, it goes through the política de envíos first — 422 `OUTBOUND_BLOCKED`, 409 `OUTBOUND_WARNING` (resend with `acknowledge`) or, for a connected app past its fan-out limit, 202 `pending_approval`. A member in the UI is not checked.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 4000,
                    "description": "The line the teammate typed (<= 800 characters once trimmed; numbers, prices and times are spoken as words). Becomes the note’s transcript."
                  },
                  "ai_agent_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Whose voice. Defaults to the agent that answers this conversation (origin agent, then the channel’s agent, then the workspace default)."
                  },
                  "acknowledge": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 20,
                    "description": "Advertencia de envío codes to go ahead over (API keys / apps only), as on `POST /conversations/{id}/messages`."
                  }
                },
                "required": [
                  "text"
                ],
                "additionalProperties": false
              },
              "example": {
                "text": "Te lo dejo en $830.000, lo retiras hoy hasta las 19:00 hrs."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Refused: `ok: false` with a `code`; nothing was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        },
                        "sent_message": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "The stored `audio` message, authored by the teammate; `metadata.attachment_context` holds the transcript."
                        },
                        "transcript": {
                          "type": "string",
                          "description": "The typed text, stored as the note’s transcript."
                        },
                        "duration_seconds": {
                          "type": "number"
                        },
                        "outcome_unknown": {
                          "type": "boolean",
                          "description": "The channel did not confirm delivery; the row is stored. Never resend."
                        },
                        "characters": {
                          "type": "number",
                          "description": "Characters sent to TTS (counted against the daily cap)."
                        },
                        "ai_agent_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Whose voice spoke."
                        },
                        "code": {
                          "type": "string",
                          "enum": [
                            "check_failed",
                            "cap_reached",
                            "not_configured",
                            "provider_error",
                            "invalid_text",
                            "agent_not_found",
                            "channel_unsupported",
                            "source_unavailable",
                            "too_large",
                            "transcode_failed",
                            "no_public_url",
                            "delivery_failed"
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "description": "`invalid_text`: empty | too_long | phone_or_rut. `cap_reached`: cap | counter_unavailable. `provider_error`: elevenlabs… | whisper."
                        },
                        "message": {
                          "type": "string",
                          "description": "Why it was refused (English)."
                        },
                        "check": {
                          "type": "object",
                          "properties": {
                            "passed": {
                              "type": "boolean"
                            },
                            "heard": {
                              "type": "string",
                              "description": "What Whisper heard."
                            },
                            "word_error_rate": {
                              "type": "number"
                            },
                            "mismatches": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "original": {
                                    "type": "string"
                                  },
                                  "expected": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "original",
                                  "expected"
                                ]
                              }
                            }
                          },
                          "required": [
                            "passed",
                            "heard",
                            "word_error_rate",
                            "mismatches"
                          ]
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "day": {
                              "type": "string",
                              "description": "UTC day, YYYY-MM-DD."
                            },
                            "characters_today": {
                              "type": "number"
                            },
                            "daily_cap": {
                              "type": "number"
                            },
                            "remaining": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "day",
                            "characters_today",
                            "daily_cap",
                            "remaining"
                          ]
                        }
                      },
                      "required": [
                        "ok"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ok": false,
                    "code": "not_configured",
                    "message": "Voice notes are off for this agent.",
                    "ai_agent_id": "6f1c2a9e-3b7d-4c55-9a51-2d8e0b7c4f10",
                    "usage": {
                      "day": "2026-10-28",
                      "characters_today": 412,
                      "daily_cap": 10000,
                      "remaining": 9588
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Sent: the stored audio message and the transcript.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean"
                        },
                        "sent_message": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "The stored `audio` message, authored by the teammate; `metadata.attachment_context` holds the transcript."
                        },
                        "transcript": {
                          "type": "string",
                          "description": "The typed text, stored as the note’s transcript."
                        },
                        "duration_seconds": {
                          "type": "number"
                        },
                        "outcome_unknown": {
                          "type": "boolean",
                          "description": "The channel did not confirm delivery; the row is stored. Never resend."
                        },
                        "characters": {
                          "type": "number",
                          "description": "Characters sent to TTS (counted against the daily cap)."
                        },
                        "ai_agent_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Whose voice spoke."
                        },
                        "code": {
                          "type": "string",
                          "enum": [
                            "check_failed",
                            "cap_reached",
                            "not_configured",
                            "provider_error",
                            "invalid_text",
                            "agent_not_found",
                            "channel_unsupported",
                            "source_unavailable",
                            "too_large",
                            "transcode_failed",
                            "no_public_url",
                            "delivery_failed"
                          ]
                        },
                        "reason": {
                          "type": "string",
                          "description": "`invalid_text`: empty | too_long | phone_or_rut. `cap_reached`: cap | counter_unavailable. `provider_error`: elevenlabs… | whisper."
                        },
                        "message": {
                          "type": "string",
                          "description": "Why it was refused (English)."
                        },
                        "check": {
                          "type": "object",
                          "properties": {
                            "passed": {
                              "type": "boolean"
                            },
                            "heard": {
                              "type": "string",
                              "description": "What Whisper heard."
                            },
                            "word_error_rate": {
                              "type": "number"
                            },
                            "mismatches": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "original": {
                                    "type": "string"
                                  },
                                  "expected": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "original",
                                  "expected"
                                ]
                              }
                            }
                          },
                          "required": [
                            "passed",
                            "heard",
                            "word_error_rate",
                            "mismatches"
                          ]
                        },
                        "usage": {
                          "type": "object",
                          "properties": {
                            "day": {
                              "type": "string",
                              "description": "UTC day, YYYY-MM-DD."
                            },
                            "characters_today": {
                              "type": "number"
                            },
                            "daily_cap": {
                              "type": "number"
                            },
                            "remaining": {
                              "type": "number"
                            }
                          },
                          "required": [
                            "day",
                            "characters_today",
                            "daily_cap",
                            "remaining"
                          ]
                        }
                      },
                      "required": [
                        "ok"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ok": true,
                    "sent_message": {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "type": "text",
                      "sender_role": "assistant",
                      "sender_type": "api_key",
                      "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": "sent",
                      "delivery_error": null,
                      "delivery_attempt": 1,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "api_key",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM propio"
                      },
                      "created_at": "2026-09-22T11:08:10.882Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "transcript": "Te lo dejo en $830.000, lo retiras hoy hasta las 19:00 hrs.",
                    "duration_seconds": 5.4,
                    "outcome_unknown": false,
                    "characters": 112,
                    "ai_agent_id": "6f1c2a9e-3b7d-4c55-9a51-2d8e0b7c4f10",
                    "usage": {
                      "day": "2026-10-28",
                      "characters_today": 524,
                      "daily_cap": 10000,
                      "remaining": 9476
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Pending approval. A connected app that already wrote to 3 distinct contacts in the last 10 minutes does not write to a new one directly: the message is parked in the approval queue («vía <app>») and goes out when a member approves it. `data.message` is Spanish, for the app to relay to its person.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PendingApproval"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`OUTBOUND_WARNING` — an Advertencia de envío (quiet hours, a thread a member is handling, a degraded number, loop protection). Nothing was sent. Resend the same request with `acknowledge` naming every code in `error.reasons` to send it anyway; the acknowledgment is recorded. (Also `IDEMPOTENCY_KEY_CONFLICT` on an Idempotency-Key reuse.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "422": {
            "description": "`OUTBOUND_BLOCKED` — a Bloqueo de envío: an opted-out contact (a single free-text reply to a customer who wrote in again after THEIR OWN opt-out passes inside the WhatsApp / Instagram / Messenger 24-hour window, or in the same email thread; a block your team set or a spam complaint still refuses), an active hold, a closed WhatsApp 24-hour window with no template, a template Meta will reject, no channel identity, a disconnected or red account. Never overridable; `error.hint` says what to do instead.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboundVerdictError"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/transcribe": {
      "post": {
        "summary": "Transcribe a recording for the composer (multipart/form-data)",
        "description": "Speech-to-text for the operator's own recording: returns the text for them to edit before sending. **Nothing is sent** and no provider is involved — contrast the per-message route below, which transcribes what the customer sent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "@nota-de-voz.m4a"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ text }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "text": "Hola, quería confirmar la hora del jueves."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/messages/{messageId}/transcribe": {
      "post": {
        "summary": "Transcribe an inbound audio message",
        "description": "Returns `{ text, cached }`. Bot-handled conversations are already transcribed by the worker, so those return the stored text with `cached: true` and no model call. Human-handled audio is transcribed on the first request — server-side, fetching the media through the provider so the browser never touches an expiring or CORS-blocked provider URL — and the result is stored, so later requests are cached too. A non-audio message is a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "messageId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ text, cached }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "text": "Hola, quería confirmar la hora del jueves.",
                    "cached": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/assign": {
      "post": {
        "summary": "Assign a conversation to a teammate",
        "description": "Moves BOTH axes in one call: `handler` (who answers — defaults to `human` here) and `assignee_user_id` (which person). `assignee_user_id: null` hands the thread to the human queue unowned; omitting the key leaves the current assignee alone. Works with or without a ticket, and mirrors onto the ticket when there is one.\n\nThis is the key-friendly counterpart of `/claim`: an API key has no person behind it and cannot claim, but it can assign to a named member.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "assignee_user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "handler": {
                    "type": "string",
                    "enum": [
                      "bot",
                      "human",
                      "external"
                    ]
                  }
                }
              },
              "example": {
                "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                "handler": "human"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The assigned conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 132,
                    "display_id": "C-132",
                    "channel": "web",
                    "external_id": "web:visitor-9f2c1a",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": null,
                    "bot_gate_override_by": null,
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:08:10.882Z",
                    "created_at": "2026-09-22T11:06:33.744Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/claim": {
      "post": {
        "summary": "Claim a conversation for the caller",
        "description": "Requires a workspace member — a Supabase session or a personal token acting as one — since the caller BECOMES the assignee, and an `sk_*` API key has no member behind it to assign the conversation to; it gets 403. An API key assigns to a SPECIFIC member instead: `POST /conversations/{id}/assign`.\n\nClaiming a thread somebody else already holds is not an error: the response reports the current assignee, so two people pressing at once get the same answer rather than a race.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The claimed conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 132,
                    "display_id": "C-132",
                    "channel": "web",
                    "external_id": "web:visitor-9f2c1a",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": null,
                    "bot_gate_override_by": null,
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:08:10.882Z",
                    "created_at": "2026-09-22T11:06:33.744Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/return-to-ai": {
      "post": {
        "summary": "Return handling to the AI",
        "description": "The inverse of `/handoff`: handler goes back to `bot` and the assignee is cleared, so the AI answers the next inbound message. Media the customer sent while a person was handling the thread is backfilled for the agent, best-effort.\n\nThis moves the HANDLING axis. It is not the same as lifting the no-auto-reply gate on a big account, which is a separate, internal operation.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The conversation, handler back to `bot`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 132,
                    "display_id": "C-132",
                    "channel": "web",
                    "external_id": "web:visitor-9f2c1a",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "status": "open",
                    "handler": "bot",
                    "assignee_user_id": null,
                    "assignee_assigned_at": null,
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": null,
                    "bot_gate_override_by": null,
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:08:10.882Z",
                    "created_at": "2026-09-22T11:06:33.744Z",
                    "updated_at": "2026-09-22T11:08:10.882Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/snooze": {
      "post": {
        "summary": "Snooze a conversation until a time or for N minutes",
        "description": "Exactly one of `until` (ISO 8601, must be in the future) or `minutes` (1 … 43 200). The thread leaves the open queue and comes back by itself; a new inbound message wakes it early. Mirrors onto the ticket when the conversation has one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "until": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 43200
                  }
                }
              },
              "example": {
                "minutes": 120
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ conversationId, status, snoozed_until }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "status": "snoozed",
                    "snoozed_until": "2026-09-22T13:07:20.217Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/pending": {
      "post": {
        "summary": "Mark a conversation pending (waiting, with no timer)",
        "description": "A snooze with nothing to wake it but the customer: the thread leaves the open queue and returns the moment they write again. Use it for \"waiting on them\"; use `/snooze` for \"come back to me at 9 am\".",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId, status }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "status": "pending"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/resolve": {
      "post": {
        "summary": "Resolve a conversation",
        "description": "\"Dealt with.\" Cascades to the ticket — but only when no OTHER conversation on that ticket is still active, so resolving the WhatsApp thread of a case that is also running by email leaves the ticket open and fires no `ticket.resolved`.\n\nA new inbound message reopens the conversation. Resolve is reversible; `/close` is the terminal one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId, status }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "status": "resolved"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/close": {
      "post": {
        "summary": "Close a conversation (terminal)",
        "description": "The terminal state, reached manually here or by the auto-close worker a grace window after a resolve. Cascades to the ticket under the same no-active-siblings rule as `/resolve`. `POST /conversations/{id}/reopen` brings it back, and so does a new inbound message.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId, status }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "status": "closed"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/reopen": {
      "post": {
        "summary": "Reopen a resolved, snoozed or closed conversation",
        "description": "Back to `open`, clearing the snooze timer and the resolution stamps. Reopening a thread whose ticket was resolved reopens the ticket too.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId, status }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001",
                    "status": "open"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/no-reply-needed": {
      "post": {
        "summary": "Mark the current wait as needing no reply («No requiere respuesta»)",
        "description": "Takes the conversation out of «Esperando respuesta» for the wait the person saw: `subject_id` is the row’s `waiting_subject_id` (the newest customer message nobody on the team answered; with none pending, the open handoff’s event). If the customer wrote again since, the call answers 409 `WAITING_SUBJECT_CHANGED` (`details.subject_id` = the current one) and records nothing. The customer’s next message starts a new wait. Recorded as a correction of the `reply_owed` verdict on that subject (label `nothing_owed`); a click outranks any verdict on the same subject, and this workspace’s later verdicts read recent clicks as examples — never another workspace’s. Idempotent (one click per verdict, concurrent clicks included): already out of the queue → `changed: false`. Other 409s carry `details.reason`: `not_waiting`, or one the queue never hides — `urgent`, `payment_obligation` (a payment held open), `promised_human_contact` (the AI told the customer a person would contact them). `subject_kind` is `message` or `handoff`; `waiting_state` is the thread’s state after the call.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "subject_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "subject_id"
                ]
              },
              "example": {
                "subject_id": "01a17d4e-c0de-7a11-9d0e-5e1a7e5b0c11"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ conversation_id, subject_id, subject_kind, waiting_state, changed }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "subject_id": "01a17d4e-c0de-7a11-9d0e-5e1a7e5b0c11",
                    "subject_kind": "message",
                    "waiting_state": "nothing_owed",
                    "changed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Undo «No requiere respuesta»",
        "description": "Removes the click `POST /conversations/{id}/no-reply-needed` recorded on the current wait (and the verdict row it created), so the conversation waits again as before. `subject_id` (optional) names the wait the undo is for: when the conversation now waits on another message, nothing changes. Nothing to undo → `changed: false`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "subject_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversation_id, subject_id, subject_kind, waiting_state, changed }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "subject_id": "01a17d4e-c0de-7a11-9d0e-5e1a7e5b0c11",
                    "subject_kind": "message",
                    "waiting_state": "nothing_owed",
                    "changed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/filter-sender": {
      "post": {
        "summary": "Denylist the sender and quarantine this conversation",
        "description": "Acts on the **sender**, not just this thread — future conversations from them are filtered too. They move to the «Filtrado» folder, which the list only shows for `?filtered=true`. Nothing is deleted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/unfilter": {
      "post": {
        "summary": "Restore a quarantined conversation to the inbox",
        "description": "Clears the quarantine **and allowlists the sender**, so the thread cannot be re-filtered later. The inverse of `/filter-sender`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ conversationId }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversationId": "bbbbbbbb-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/notes": {
      "get": {
        "summary": "List internal notes on a conversation",
        "description": "Internal only — a note is never delivered to the customer and never appears in the transcript the export renders as messages.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Notes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "ffffffff-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "author_user_id": "11111111-0000-4000-8000-000000000001",
                      "author_name": "Camila Rojas",
                      "body": "El cliente ya envió la boleta por correo.",
                      "created_at": "2026-09-22T11:06:39.044Z",
                      "updated_at": "2026-09-22T11:06:39.044Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add an internal note",
        "description": "`@mentions` in the body are parsed into mention records (matched on the email local-part) and notify the named teammates. That parsing is best-effort: it never fails the note.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  }
                },
                "required": [
                  "body"
                ]
              },
              "example": {
                "body": "El cliente ya envió la boleta por correo."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created note",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "ffffffff-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_name": "Camila Rojas",
                    "body": "El cliente ya envió la boleta por correo.",
                    "created_at": "2026-09-22T11:06:39.044Z",
                    "updated_at": "2026-09-22T11:06:39.044Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/notes/{noteId}": {
      "delete": {
        "summary": "Delete an internal note",
        "description": "The note is deleted, not archived. There is no undo.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "noteId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit an internal note",
        "description": "Replaces the body. Editing does NOT re-parse `@mentions`, so a name added in an edit notifies nobody — post a new note for that.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "noteId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "body": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 8000
                  }
                },
                "required": [
                  "body"
                ]
              },
              "example": {
                "body": "El cliente ya envió la boleta por correo. @camila ¿la revisas?"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated note",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "ffffffff-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "author_user_id": "11111111-0000-4000-8000-000000000001",
                    "author_name": "Camila Rojas",
                    "body": "El cliente ya envió la boleta por correo. @camila ¿la revisas?",
                    "created_at": "2026-09-22T11:06:39.044Z",
                    "updated_at": "2026-09-22T11:10:12.500Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/tags": {
      "get": {
        "summary": "List the tags on a conversation",
        "description": "Gated on `tags:read`, not `conversations:read` — a caller that can read the thread but not the taxonomy gets 403 here and still reads the conversation.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags on the conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "12121212-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "garantia",
                      "display_name": "Garantía",
                      "usage_count": 1,
                      "created_at": "2026-09-22T11:06:38.372Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Attach a tag to a conversation",
        "description": "Send **either** `tag_id` to attach an existing tag **or** `name` to create-and-attach in one call — the body is one or the other, not a mix. `name` is slugified, so attaching «Garantía» twice attaches the same tag twice over, never two. Gated on `tags:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "anyOf": [
                  {
                    "type": "object",
                    "properties": {
                      "tag_id": {
                        "type": "string",
                        "format": "uuid"
                      }
                    },
                    "required": [
                      "tag_id"
                    ]
                  },
                  {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      }
                    },
                    "required": [
                      "name"
                    ]
                  }
                ]
              },
              "example": {
                "name": "Garantía"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The attached tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "12121212-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "garantia",
                    "display_name": "Garantía",
                    "usage_count": 1,
                    "created_at": "2026-09-22T11:06:38.372Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/tags/{tag_id}": {
      "delete": {
        "summary": "Detach a tag from a conversation",
        "description": "Removes the link only — the tag itself survives for every other conversation, and re-attaching the same pair restores it. Deleting the tag is `DELETE /tags/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "tag_id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/attributes": {
      "get": {
        "summary": "List the attribute values on a conversation",
        "description": "The values stored on this thread, each joined to its DEFINITION (`custom_attribute_id`, `label`, `data_type`, `options`, `required`) so a client can render the field without a second read. `source` says who wrote it — `admin` for a person or an integration, or the AI tool that filled it. A key with no definition still appears, with the definition fields null: the values join by TEXT key, not by a foreign key.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Attributes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "14141414-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "key": "numero_de_caso",
                      "value": "F-4821",
                      "source": "admin",
                      "source_tool": null,
                      "custom_attribute_id": "13131313-0000-4000-8000-000000000001",
                      "label": "Número de caso",
                      "data_type": "text",
                      "options": [],
                      "required": false,
                      "multiline": false,
                      "unique_values": false,
                      "pinned": false,
                      "updated_at": "2026-09-22T11:06:46.763Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Upsert attribute values on a conversation",
        "description": "Upsert, not replace: the keys in the body are written, everything else on the thread is left alone. 1 … 50 pairs per call; `value` is any JSON value. A key defined with `unique_values` collides with a 409 rather than overwriting the other record.\n\nDeleting a value is `DELETE /conversations/{id}/attributes/{key}` — sending `null` stores a null, which is a different thing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "attributes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
                        },
                        "value": {
                          "type": "string",
                          "description": "Any JSON value (string, number, boolean, null, array, object)"
                        }
                      },
                      "required": [
                        "key"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 50
                  }
                },
                "required": [
                  "attributes"
                ],
                "additionalProperties": false
              },
              "example": {
                "attributes": [
                  {
                    "key": "numero_de_caso",
                    "value": "F-4821"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The written attributes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "14141414-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "key": "numero_de_caso",
                      "value": "F-4821",
                      "source": "admin",
                      "source_tool": null,
                      "is_unique": false,
                      "created_at": "2026-09-22T11:06:46.884Z",
                      "updated_at": "2026-09-22T11:06:46.763Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/attributes/{key}": {
      "delete": {
        "summary": "Delete one attribute value from a conversation",
        "description": "Removes the VALUE from this conversation. The definition survives, and so does the same key on every other thread.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120,
              "pattern": "^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$"
            },
            "required": true,
            "name": "key",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/conversations/{id}/tickets": {
      "post": {
        "summary": "Open a ticket on a conversation",
        "description": "The \"long-running case\" path — the service handles default-team routing, auto-assignment and the `ticket.opened` Evento. Gated on `tickets:write`, not `conversations:write`. `stage_id` places the ticket on a column of a `ticket` board; omit it and it lands on the workspace's fallback ticket board. A conversation that already has a ticket answers with that one rather than opening a second.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pipeline_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "stage_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  }
                }
              },
              "example": {
                "stage_id": "55555555-0000-4000-8000-000000000011",
                "reason": "El cliente pide reagendar su visita"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created ticket",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 6,
                    "display_id": "T-6",
                    "status": "open",
                    "handler": "bot",
                    "assignee_user_id": null,
                    "assignee_ai_agent_graph_id": null,
                    "team_id": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "lead_id": null,
                    "origin_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "current_stage_id": "55555555-0000-4000-8000-000000000011",
                    "snoozed_until": null,
                    "reason": "El cliente pide reagendar su visita",
                    "opened_by": "human",
                    "opened_via": "admin_ui",
                    "resolved_by": null,
                    "resolved_at": null,
                    "brand": null,
                    "created_at": "2026-09-22T11:06:47.738Z",
                    "updated_at": "2026-09-22T11:06:54.183Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/attach-to-ticket": {
      "post": {
        "summary": "Attach this conversation to an existing ticket",
        "description": "Cross-channel follow-up: the customer who asked by WhatsApp writes again by email, and both threads belong to one case. Attaching puts this conversation on that ticket, so `GET /tickets/{id}/conversations` lists both and `GET /tickets/{id}/messages` interleaves them into one timeline. The response reports `previous_ticket_id` when the conversation was moved off another ticket.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ticket_id": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "ticket_id"
                ]
              },
              "example": {
                "ticket_id": "dddddddd-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new linkage, incl. `previous_ticket_id`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000002",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "previous_ticket_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/detach-from-ticket": {
      "post": {
        "summary": "Detach this conversation from its ticket",
        "description": "Leaves the ticket standing; only this conversation's link to it is removed. Detaching the last conversation does not close the ticket.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ previous_ticket_id }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "previous_ticket_id": "dddddddd-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/calls": {
      "get": {
        "summary": "List a conversation's call segments",
        "description": "A voice conversation is a thread like any other: the transcript is on `/messages`, and the CALLS — one row per connected segment, with its direction, duration and whether a recording exists — are here. Empty on every non-voice thread.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Calls",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "17171717-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "direction": "outbound",
                      "status": "completed",
                      "started_at": "2026-09-22T11:12:00.000Z",
                      "ended_at": "2026-09-22T11:14:37.000Z",
                      "duration_seconds": 157,
                      "recording_available": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/calls/{call_id}/recording-url": {
      "get": {
        "summary": "Mint a short-lived playback URL for a call recording",
        "description": "A presigned URL that expires in minutes, so a recording is never served from a link that can be forwarded or logged. Mint a new one per playback rather than storing this. A call with no recording is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "call_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ url, expires_at }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://media.vitrinadev.com/calls/17171717/recording.mp3?X-Amz-Expires=300&…",
                    "expires_at": "2026-09-22T11:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/linked-conversations": {
      "get": {
        "summary": "List a conversation's linked conversations",
        "description": "Conversaciones enlazadas: the same customer in ONE exchange over two channels — a source conversation (e.g. Facebook Marketplace) and its continuation (e.g. WhatsApp). Each conversation keeps its own control. Every link is returned from THIS conversation's side: `role` says whether it is the `source` or the `continuation`, and `linked_conversation` is the other one. `context_card` is what the continuation carries from the source (the car, what was agreed, what is pending); it is never a message. Newest link first; empty when the conversation has no links. A member who sees only their own conversations does not get a link whose other conversation they cannot see.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                      "role": "source",
                      "origin": "manual",
                      "source_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                      "context_card": {
                        "summary": "Pregunta por la Hilux 2021 publicada en Marketplace; quiere verla el sábado.",
                        "vehicle": {
                          "vehicle_id": "8d1b6e3a-4c27-4f90-b5a8-1e7c3d9f2a64",
                          "label": "Toyota Hilux 2.8 SRX 4x4 2021"
                        },
                        "agreements": [
                          "Visita el sábado a las 11:00 en la sucursal Las Condes"
                        ],
                        "pending_asks": [
                          "Fotos del interior y del tablero"
                        ],
                        "pending_promises": [
                          "Enviar la ficha técnica por WhatsApp"
                        ],
                        "trade_in": "Entrega en parte de pago un Suzuki Swift 2017, 98.000 km"
                      },
                      "created_by_user_id": null,
                      "created_at": "2026-10-15T14:02:11.000Z",
                      "linked_conversation": {
                        "id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                        "display_id": "C-3061",
                        "channel": "whatsapp",
                        "status": "open",
                        "last_message_date": "2026-10-15T14:05:40.000Z"
                      }
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Link a conversation to its continuation on another channel",
        "description": "`{id}` is the SOURCE conversation; `continuation_conversation_id` is where the exchange continues. Both must belong to the same contact (merge the contacts first) and run on different channels. Idempotent per pair, in either direction: `201` when the link is created, `200` with the existing link when the two were already linked — a `context_card` in the body then replaces the stored one. Nothing is sent to the customer. Both conversations must be visible to the caller: one they cannot see is a 404, like one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "continuation_conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "context_card": {
                    "type": "object",
                    "properties": {
                      "summary": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 1000
                      },
                      "vehicle": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "vehicle_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "label": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 200
                          }
                        },
                        "required": [
                          "label"
                        ]
                      },
                      "agreements": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300
                        },
                        "maxItems": 10
                      },
                      "pending_asks": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300
                        },
                        "maxItems": 10
                      },
                      "pending_promises": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 300
                        },
                        "maxItems": 10
                      },
                      "trade_in": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "continuation_conversation_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                "context_card": {
                  "summary": "Pregunta por la Hilux 2021 publicada en Marketplace; quiere verla el sábado.",
                  "vehicle": {
                    "vehicle_id": "8d1b6e3a-4c27-4f90-b5a8-1e7c3d9f2a64",
                    "label": "Toyota Hilux 2.8 SRX 4x4 2021"
                  },
                  "agreements": [
                    "Visita el sábado a las 11:00 en la sucursal Las Condes"
                  ],
                  "pending_asks": [
                    "Fotos del interior y del tablero"
                  ],
                  "pending_promises": [
                    "Enviar la ficha técnica por WhatsApp"
                  ],
                  "trade_in": "Entrega en parte de pago un Suzuki Swift 2017, 98.000 km"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The existing link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                    "role": "source",
                    "origin": "manual",
                    "source_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                    "context_card": {
                      "summary": "Pregunta por la Hilux 2021 publicada en Marketplace; quiere verla el sábado.",
                      "vehicle": {
                        "vehicle_id": "8d1b6e3a-4c27-4f90-b5a8-1e7c3d9f2a64",
                        "label": "Toyota Hilux 2.8 SRX 4x4 2021"
                      },
                      "agreements": [
                        "Visita el sábado a las 11:00 en la sucursal Las Condes"
                      ],
                      "pending_asks": [
                        "Fotos del interior y del tablero"
                      ],
                      "pending_promises": [
                        "Enviar la ficha técnica por WhatsApp"
                      ],
                      "trade_in": "Entrega en parte de pago un Suzuki Swift 2017, 98.000 km"
                    },
                    "created_by_user_id": null,
                    "created_at": "2026-10-15T14:02:11.000Z",
                    "linked_conversation": {
                      "id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                      "display_id": "C-3061",
                      "channel": "whatsapp",
                      "status": "open",
                      "last_message_date": "2026-10-15T14:05:40.000Z"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "The created link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                    "role": "source",
                    "origin": "manual",
                    "source_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                    "context_card": {
                      "summary": "Pregunta por la Hilux 2021 publicada en Marketplace; quiere verla el sábado.",
                      "vehicle": {
                        "vehicle_id": "8d1b6e3a-4c27-4f90-b5a8-1e7c3d9f2a64",
                        "label": "Toyota Hilux 2.8 SRX 4x4 2021"
                      },
                      "agreements": [
                        "Visita el sábado a las 11:00 en la sucursal Las Condes"
                      ],
                      "pending_asks": [
                        "Fotos del interior y del tablero"
                      ],
                      "pending_promises": [
                        "Enviar la ficha técnica por WhatsApp"
                      ],
                      "trade_in": "Entrega en parte de pago un Suzuki Swift 2017, 98.000 km"
                    },
                    "created_by_user_id": null,
                    "created_at": "2026-10-15T14:02:11.000Z",
                    "linked_conversation": {
                      "id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                      "display_id": "C-3061",
                      "channel": "whatsapp",
                      "status": "open",
                      "last_message_date": "2026-10-15T14:05:40.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/continue-on-whatsapp": {
      "post": {
        "summary": "Continue a conversation on WhatsApp",
        "description": "Continuación por WhatsApp: the dealer writes to the customer on WhatsApp to continue THIS conversation (Instagram, Messenger, web chat, voice or Marketplace — never a WhatsApp conversation), links the two conversations and hands over what was agreed. `phone` is optional: omitted, the number captured in the conversation (else the contact's) is used. The send is scheduled, never synchronous. Idempotent per (conversation, phone): asking again returns the existing Continuación (`outcome: already`, or `linked` when the customer already continues on that number from another conversation) and never sends twice; a refused or failed one is re-armed. `state` is `pending` (scheduled), `deferred` (waiting for an approved WhatsApp template, sent as soon as it is approved and never after `send_deadline_at`), `sent`, `failed` or `skipped`. A refusal is a `422` whose `details.reason` says why (`no_phone`, `no_whatsapp`, `do_not_contact`, `dealer_number`, `buyer_wrote_first`, `unsupported_channel`, `billing_paused`, `no_name`, ...). A conversation the caller cannot see is a `404`, like one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 5,
                    "maxLength": 40
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "phone": "+56 9 8765 4321"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The Continuación and what this request did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "scheduled",
                    "reason": null,
                    "continuation_id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                    "state": "pending",
                    "status": "pending",
                    "phone": "+56987654321",
                    "source_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                    "link_id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                    "merged_into_contact_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/whatsapp-continuations": {
      "get": {
        "summary": "List a conversation's Continuaciones por WhatsApp",
        "description": "The Continuaciones started from this conversation, newest first (one per phone number). Read `state` to draw the inbox button: `pending`, `deferred` (waiting for an approved template, until `send_deadline_at`), `sent`, `failed` or `skipped` (`reason` says why). Empty when none was started. `meta.whatsapp_line` is `true` when the workspace has an enabled WhatsApp line a Continuación could use (the same lookup the send runs), so a caller without `messaging_accounts:read` can still decide whether to offer the button. A conversation the caller cannot see is a `404`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Continuaciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0c4f7a92-1e6b-4d38-8a5f-63b2e9d1c470",
                      "state": "deferred",
                      "status": "pending",
                      "reason": "template_not_approved",
                      "phone": "+56987654321",
                      "trigger": "user",
                      "source_conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "continuation_conversation_id": "5a8e2c41-7b3d-4f19-9c06-2d7e4b1a8f53",
                      "captured_at": "2026-10-15T14:02:11.000Z",
                      "send_deadline_at": "2026-10-16T14:02:11.000Z",
                      "sent_at": null,
                      "created_at": "2026-10-15T14:02:11.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "whatsapp_line": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/agent-turn": {
      "get": {
        "summary": "Inspect the exact state the AI agent would receive",
        "description": "Rebuilds, through the same assembler the worker uses, everything the agent sees on this conversation: the full system prompt, the gated tool catalogue, message history, temperature and step budget. The model and reasoning effort are platform-managed and not included. Read-only — no enrichment, no tool side effects. Answers a JSON **file download** (`Content-Disposition: attachment`), not the `{ data }` envelope the other reads use. On a clinic, a connected app reads the patient in the prompt, history and tool results as a pseudonym unless the clinic allows patient names, like every other read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The agent-turn bundle as a JSON attachment",
            "headers": {
              "Content-Disposition": {
                "description": "attachment; filename=\"agent-turn-<id>.json\"",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": {}
                },
                "example": {
                  "inspected_at": "2026-10-09T16:40:00.000Z",
                  "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                  "display_id": "C-132",
                  "channel": "web",
                  "agent_version": null,
                  "ticket": {
                    "id": "dddddddd-0000-4000-8000-000000000001",
                    "display_id": "T-6",
                    "status": "open",
                    "current_stage_id": "55555555-0000-4000-8000-000000000011"
                  },
                  "agent": {
                    "id": "7b7b7b7b-0000-4000-8000-000000000001",
                    "name": "Asistente de ventas"
                  },
                  "temperature": 0.3,
                  "max_steps": 8,
                  "system_prompt": "Eres el asistente de ventas de Automotora Ejemplo. Responde en español, con tono cercano…",
                  "messages": [
                    {
                      "role": "user",
                      "content": "Hola, ¿siguen disponibles los repuestos que vi en la web?"
                    },
                    {
                      "role": "assistant",
                      "content": "Hola María, sí. ¿Qué repuesto te interesa?"
                    }
                  ],
                  "history_summary": null,
                  "tools": [
                    {
                      "type": "function",
                      "function": {
                        "name": "search_products",
                        "description": "Busca productos del catálogo por texto, marca o precio.",
                        "parameters": {
                          "type": "object",
                          "properties": {
                            "q": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ],
                  "knowledge_base": [
                    {
                      "name": "Preguntas frecuentes.pdf"
                    }
                  ],
                  "skills": [],
                  "workspace": {
                    "language": "es",
                    "glossary": [],
                    "timezone": "America/Santiago",
                    "withinBusinessHours": true,
                    "businessHoursReason": null,
                    "currency": "CLP",
                    "dateFormat": "latam"
                  },
                  "branding": {
                    "displayName": "Automotora Ejemplo",
                    "senderName": "Automotora Ejemplo",
                    "website": "https://ejemplo.cl"
                  },
                  "vertical": "automotive",
                  "connected_channels": [
                    {
                      "name": "WhatsApp Ventas",
                      "channels": [
                        "whatsapp"
                      ]
                    }
                  ],
                  "branches": [],
                  "pipelines": [],
                  "sales_pipelines": [],
                  "open_leads": [],
                  "previous_conversations": {
                    "items": [],
                    "olderCount": 0
                  },
                  "business_hours": null,
                  "attributes": [],
                  "contact_attributes": []
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/ai-control": {
      "patch": {
        "summary": "Keep a conversation with people across AI availability windows",
        "description": "`keep_with_human: true` pins the conversation to its human handler: the AI stays silent on it even after an AI availability window opens, until you send `false`. If the conversation is not yet handled by a person it is first switched to the human handler (keeping the current assignee, if any). `false` releases the pin and lets the normal availability rules apply again. Scoped to this one conversation. Needs the `conversations:write` scope; 404 when the conversation does not exist in the workspace. Answers the resulting control state: `source`, `revision`, `changed_at` and `blocked_until`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "keep_with_human": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "keep_with_human"
                ],
                "additionalProperties": false
              },
              "example": {
                "keep_with_human": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversation AI control",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "keep_with_human": true,
                    "source": "persistent_human",
                    "changed_at": "2026-10-09T16:41:00.000Z",
                    "revision": 3,
                    "blocked_until": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/bot-gate-override": {
      "post": {
        "summary": "Let the AI answer this conversation despite the no-auto-reply gate",
        "description": "`enabled: true` stamps a per-conversation override so the AI may answer even though the contact is over the workspace follower threshold (or is marked \"sin bot\"); `false` re-arms the gate. Scoped to this conversation only — it changes nothing for the contact or for any other thread. Read the current state from `GET /conversations/{id}/ai-status` (`bot_gate_override_at`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conversation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "bbbbbbbb-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "display_seq": 132,
                    "display_id": "C-132",
                    "channel": "web",
                    "external_id": "web:visitor-9f2c1a",
                    "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                    "brand": null,
                    "contact_id": "22222222-0000-4000-8000-000000000001",
                    "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                    "status": "open",
                    "handler": "human",
                    "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                    "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                    "snoozed_until": null,
                    "resolved_at": null,
                    "resolved_by": null,
                    "closed_at": null,
                    "close_deferred_until": null,
                    "transfer_immediately": false,
                    "filtered_at": null,
                    "filtered_reason": null,
                    "summary": null,
                    "summarized_at": null,
                    "history_summary": null,
                    "history_summary_upto_message_id": null,
                    "first_billable_at": null,
                    "billable": null,
                    "billing_reason": null,
                    "assigned_unanswered_alerted_at": null,
                    "assigned_unanswered_realerted_at": null,
                    "bot_gate_override_at": "2026-10-09T16:42:10.000Z",
                    "bot_gate_override_by": "11111111-0000-4000-8000-000000000001",
                    "source": null,
                    "awaiting_human_since": null,
                    "awaiting_payment_since": null,
                    "awaiting_payment_escalated_at": null,
                    "awaiting_obligation_id": null,
                    "ad_referral": null,
                    "ad_join": null,
                    "ad_welcome": null,
                    "metadata": null,
                    "ai_control_source": "legacy",
                    "ai_control_changed_at": null,
                    "ai_control_revision": 0,
                    "ai_control_window_start": null,
                    "ai_control_window_end": null,
                    "ai_control_account_revision": null,
                    "ai_blocked_until": null,
                    "ai_keep_with_human": false,
                    "external_id_provisional": false,
                    "originated_at": null,
                    "originated_by_kind": null,
                    "originated_by_ai_agent_id": null,
                    "originated_by_user_id": null,
                    "originated_outbound_action_id": null,
                    "last_message_date": "2026-09-22T11:08:10.882Z",
                    "created_at": "2026-09-22T11:06:33.744Z",
                    "updated_at": "2026-09-22T11:08:10.882Z",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl",
                      "phone": "+56 9 1234 5678"
                    },
                    "ticket": {
                      "id": "dddddddd-0000-4000-8000-000000000001",
                      "display_id": "T-6",
                      "status": "open",
                      "current_stage_id": "55555555-0000-4000-8000-000000000011"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/conversations/{id}/media/sign": {
      "post": {
        "summary": "Sign conversation media urls for a short-lived read",
        "description": "Stored media urls (`message.media_urls`, `media_playback`) are references, not links: the public media proxy answers `403` to an unsigned url and `410` to an expired one (vitrina-app#5263). Trade them here for urls that work for 15 minutes (`expires_at`), then sign again — a client re-signs before expiry, or on a `410`.\n\nOnly urls whose object belongs to THIS conversation are signed; one from another thread comes back with `error: \"forbidden\"`. A url that is not conversation media (a channel CDN link) is echoed back in `signed_url` with `expires_at: null`. A healthcare workspace's urls are served `Cache-Control: no-store`. Never log or persist a signed url: it is a bearer credential until it expires.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Conversations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "urls": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 2048
                    },
                    "minItems": 1,
                    "maxItems": 100
                  }
                },
                "required": [
                  "urls"
                ],
                "additionalProperties": false
              },
              "example": {
                "urls": [
                  "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One row per url, in request order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "url": "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg",
                    "signed_url": "https://api.vitrina.example/api/v1/public/conversation-media/bbbbbbbb-0000-4000-8000-000000000001/0b7f2c51-voice.ogg?exp=1793000000&g=s&ns=1&sig=REDACTED",
                    "expires_at": "2026-10-23T12:15:00.000Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/custom-attributes": {
      "get": {
        "summary": "List attribute definitions",
        "description": "The catalogue, optionally narrowed to one `entity_type`. Every row carries `value_count` and `record_count` — how much data is stored under that key and across how many records — so a delete dialog can state the blast radius without a second call.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "contact",
                "conversation",
                "ticket",
                "company"
              ]
            },
            "required": false,
            "name": "entity_type",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Attribute definitions with their usage counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "13131313-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "entity_type": "conversation",
                      "key": "numero_de_caso",
                      "label": "Número de caso",
                      "data_type": "text",
                      "options": [],
                      "description": "El folio que el cliente cita al escribir",
                      "required": false,
                      "display_order": 1,
                      "active": true,
                      "multiline": false,
                      "unique_values": false,
                      "created_at": "2026-09-22T11:06:33.354Z",
                      "updated_at": "2026-09-22T11:06:33.354Z",
                      "value_count": 1,
                      "record_count": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Define an attribute",
        "description": "`key` is lowercase alphanumeric with underscores and is **permanent** — there is no rename, here or later.\n\n**Creating a key adopts data.** If values were already stored under it, this definition takes them over immediately: they gain a label and a type without being rewritten. That is the intended way to formalise what the agent has been collecting, and it means a create is not always a blank field.\n\nBecause of that, a create asking for `unique_values` runs the same duplicate scan a later flip would, and 409s the same way — a workspace can never acquire a unique field that its existing data already violates. `unique_values` means no two records in the workspace may hold the same value, compared trimmed and case-insensitively; empty and null values never collide. It cannot be set on a `file` or `boolean` attribute — a unique document is meaningless and a unique boolean would cap the whole workspace at two records.\n\nTurning it ON scans the existing data first and answers **409** listing the offending values and the records holding them, capped at 20 values with up to 10 records each. **A 409 means nothing changed** — the scan runs before anything is written and the rest of the change lands in one transaction with it, so a rename and a uniqueness flip in the same save either both apply or neither does. Turning it OFF is always allowed.\n\n`data_type` shapes how values are validated when written. `rut` módulo-11 validates and stores the Chilean tax id canonically (`19312475-5`); `file` is contact-scoped ONLY, since documents are stored per contact and no other scope has an upload path. Type validation is write-time and never retroactive: changing a key to `rut` does not re-check or rewrite values already stored under it.\n\nAnswers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "entity_type": {
                    "type": "string",
                    "enum": [
                      "contact",
                      "conversation",
                      "ticket",
                      "company"
                    ]
                  },
                  "key": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "data_type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "boolean",
                      "date",
                      "select",
                      "multiselect",
                      "url",
                      "email",
                      "file",
                      "rut"
                    ]
                  },
                  "options": {
                    "type": "array",
                    "items": {}
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "required": {
                    "type": "boolean"
                  },
                  "display_order": {
                    "type": "integer"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "multiline": {
                    "type": "boolean"
                  },
                  "unique_values": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "entity_type",
                  "key",
                  "label"
                ]
              },
              "example": {
                "entity_type": "conversation",
                "key": "numero_de_caso",
                "label": "Número de caso",
                "data_type": "text",
                "description": "El folio que el cliente cita al escribir",
                "display_order": 1
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created definition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "13131313-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "entity_type": "conversation",
                    "key": "numero_de_caso",
                    "label": "Número de caso",
                    "data_type": "text",
                    "options": [],
                    "description": "El folio que el cliente cita al escribir",
                    "required": false,
                    "display_order": 1,
                    "active": true,
                    "multiline": false,
                    "unique_values": false,
                    "created_at": "2026-09-22T11:06:33.354Z",
                    "updated_at": "2026-09-22T11:06:33.354Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Existing values already violate the requested uniqueness"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/custom-attributes/undefined-keys": {
      "get": {
        "summary": "Keys with data but no definition",
        "description": "The \"Claves sin definición\" panel: attribute keys that have stored VALUES in this scope but no definition describing them — usually because the agent captured something nobody had declared yet.\n\nEach row reports `uses`, `distinct_records` and a breakdown by `source` (`tool`, `admin`, `system`, `agent`), so a busy key written by the agent is distinguishable from a one-off typo. Ordered most-used first, 200 keys per page by default: `limit` (1–200) and `offset` page through the rest, and `meta.pagination.total` is the number of undefined keys in the scope — when it exceeds the page, the list is partial.\n\nFrom here a key has two ends: `POST /custom-attributes` with the same key adopts it and every value under it, or `DELETE /custom-attributes/values` clears it away.\n\n`entity_type` is required and limited to `contact` or `conversation` — the other two scopes store no per-record values, so the question does not apply. Registered before `/{id}`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "contact",
                "conversation"
              ]
            },
            "required": true,
            "name": "entity_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Undefined keys with usage stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "Talla camisa",
                      "entity_type": "conversation",
                      "uses": 3,
                      "distinct_records": 3,
                      "sources": {
                        "tool": 3,
                        "admin": 0,
                        "system": 0,
                        "agent": 0
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 200,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/custom-attributes/{id}": {
      "get": {
        "summary": "Fetch one definition",
        "description": "The definition on its own, without the `value_count` / `record_count` aggregates the list carries.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The definition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "13131313-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "entity_type": "conversation",
                    "key": "numero_de_caso",
                    "label": "Número de caso",
                    "data_type": "text",
                    "options": [],
                    "description": "El folio que el cliente cita al escribir",
                    "required": false,
                    "display_order": 1,
                    "active": true,
                    "multiline": false,
                    "unique_values": false,
                    "created_at": "2026-09-22T11:06:33.354Z",
                    "updated_at": "2026-09-22T11:06:33.354Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a definition",
        "description": "A PUT that behaves as a PATCH; at least one field is required. **`key` and `entity_type` are not accepted** — both are create-only, because changing either would orphan every value stored under the old pair. They are stripped from the body rather than rejected, so sending them silently does nothing.\n\n`unique_values` means no two records in the workspace may hold the same value, compared trimmed and case-insensitively; empty and null values never collide. It cannot be set on a `file` or `boolean` attribute — a unique document is meaningless and a unique boolean would cap the whole workspace at two records.\n\nTurning it ON scans the existing data first and answers **409** listing the offending values and the records holding them, capped at 20 values with up to 10 records each. **A 409 means nothing changed** — the scan runs before anything is written and the rest of the change lands in one transaction with it, so a rename and a uniqueness flip in the same save either both apply or neither does. Turning it OFF is always allowed.\n\nChanging `data_type` to `file` is refused unless the definition is contact-scoped. Changing a type does NOT re-validate or convert stored values — they keep whatever they hold until something writes them again, so a switch to `rut` or `number` can leave rows that would not be accepted today.\n\n`options` is replaced wholesale. Removing an option a record already holds does not clear that record’s value.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "data_type": {
                    "type": "string",
                    "enum": [
                      "text",
                      "number",
                      "boolean",
                      "date",
                      "select",
                      "multiselect",
                      "url",
                      "email",
                      "file",
                      "rut"
                    ]
                  },
                  "options": {
                    "type": "array",
                    "items": {}
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "required": {
                    "type": "boolean"
                  },
                  "display_order": {
                    "type": "integer"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "multiline": {
                    "type": "boolean"
                  },
                  "unique_values": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "required": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated definition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "13131313-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "entity_type": "conversation",
                    "key": "numero_de_caso",
                    "label": "Número de caso",
                    "data_type": "text",
                    "options": [],
                    "description": "El folio que el cliente cita al escribir",
                    "required": true,
                    "display_order": 1,
                    "active": true,
                    "multiline": false,
                    "unique_values": false,
                    "created_at": "2026-09-22T11:06:33.354Z",
                    "updated_at": "2026-09-22T11:06:33.354Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Existing values already violate the requested uniqueness"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a definition",
        "description": "**Non-destructive by default.** Removes the definition and leaves every stored value in place as an orphan — invisible in the UI, still in the database, and re-adopted automatically if the key is ever created again. Deleting a field is therefore reversible; that is the point.\n\n`?purge_values=true` opts in to destroying the data as well, including reaping uploaded files. It matters most for a `file` attribute, where the \"harmless orphan\" is a customer’s document still sitting in the bucket and still downloadable.\n\nThe flag **fails closed**: anything that is not exactly `true` — absent, `false`, `1`, a typo — leaves the data alone. Values are purged BEFORE the definition is removed, so a failed purge leaves both intact and the call retryable, rather than a stranded key whose size nobody can see any more.\n\nA purge on a `ticket` or `company` definition is a truthful no-op — those scopes have no value table.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "purge_values",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/custom-attributes/values": {
      "delete": {
        "summary": "Destroy the stored values of a key",
        "description": "**Destructive and not undoable.** Deletes every value under one (`entity_type`, `key`) across the whole workspace and reaps the uploaded file behind each `file` value. Answers `{ removed, files_reaped }`.\n\nThis targets DATA, never the catalogue. Called on a stranded key it finishes the cleanup `undefined-keys` started; called on a key that still has a definition it empties the field and leaves the field in place, which is a legitimate \"start this field over\".\n\n`key` is taken **verbatim** and is deliberately not normalised — keys written before write-time normalisation existed can look like `\"Talla camisa\"`, and normalising the parameter would make exactly those stranded rows unpurgeable. Send the key exactly as `undefined-keys` reports it.\n\nFile reaping is best-effort: the rows are gone before storage is touched, so a bucket failure lowers `files_reaped` rather than failing the purge. Registered before `/{id}` so `values` is never read as an id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Attributes"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "contact",
                "conversation"
              ]
            },
            "required": true,
            "name": "entity_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "required": true,
            "name": "key",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ removed, files_reaped }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "removed": 3,
                    "files_reaped": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/custom-tools": {
      "get": {
        "summary": "List the workspace’s custom tools",
        "description": "Every tool with its parameters, auth configuration and request template, plus a `total`. Credential VALUES are not here — `auth_config` carries a `credential_id` reference, not a secret.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "responses": {
          "200": {
            "description": "Custom tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a7a7a7a7-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "consultar_clima",
                      "description": "Consulta el clima actual de una ciudad",
                      "kind": "custom_http",
                      "enabled": true,
                      "parameters": [
                        {
                          "name": "ciudad",
                          "type": "string",
                          "description": "Nombre de la ciudad",
                          "required": true
                        }
                      ],
                      "auth_type": "none",
                      "auth_config": {},
                      "request_template": {
                        "method": "GET",
                        "url": "https://api.example.com/weather?city=${param.ciudad}",
                        "query": [],
                        "headers": [],
                        "body_kind": "none"
                      },
                      "response_schema": null,
                      "sample_response": null,
                      "timeout_ms": 15000,
                      "volatile": false,
                      "created_at": "2026-09-15T12:00:00.000Z",
                      "updated_at": "2026-09-15T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a custom tool",
        "description": "`name` is the function name the model sees and must be snake_case; it is unique per workspace, and a collision is a **409**. `description` is what the model reads to decide whether to call the tool, so it is required and is prompt text rather than documentation.\n\nEach entry in `parameters` becomes an argument the model can supply, and its `description` is likewise read by the model. Their values reach the request as `${param.NAME}` placeholders anywhere in the URL, query, headers or body.\n\nAny `auth_type` other than `none` requires `auth_config.credential_id` pointing at a stored credential (`/tool-credentials`). Secrets are never written inline here — the template references them as `${secret.NAME}` and the runtime substitutes them.\n\nThe template is validated on the way in: the URL must be absolute http(s), a `GET` may not carry a body, and a `json` body must parse once placeholders are substituted. `timeout_ms` defaults to 15.000 and is capped at 60.000. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 64,
                    "pattern": "^[a-z][a-z0-9_]{1,63}$"
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "parameters": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,63}$"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "integer",
                            "number",
                            "boolean"
                          ]
                        },
                        "description": {
                          "type": "string",
                          "maxLength": 500,
                          "default": ""
                        },
                        "required": {
                          "type": "boolean",
                          "default": false
                        }
                      },
                      "required": [
                        "name",
                        "type"
                      ]
                    },
                    "maxItems": 40,
                    "default": []
                  },
                  "auth_type": {
                    "type": "string",
                    "enum": [
                      "none",
                      "api_key",
                      "bearer",
                      "basic",
                      "oauth2",
                      "static_header"
                    ],
                    "default": "none"
                  },
                  "auth_config": {
                    "type": "object",
                    "properties": {
                      "credential_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "placement": {
                        "type": "string",
                        "enum": [
                          "header",
                          "query",
                          "cookie"
                        ],
                        "default": "header"
                      },
                      "header_name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "query_param": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "prefix": {
                        "type": "string",
                        "maxLength": 40
                      }
                    },
                    "default": {}
                  },
                  "request_template": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "GET",
                          "POST",
                          "PUT",
                          "PATCH",
                          "DELETE"
                        ]
                      },
                      "url": {
                        "type": "string",
                        "minLength": 8,
                        "maxLength": 2048
                      },
                      "headers": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "query": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "body_kind": {
                        "type": "string",
                        "enum": [
                          "none",
                          "json",
                          "form",
                          "raw"
                        ],
                        "default": "none"
                      },
                      "body": {
                        "type": "string",
                        "maxLength": 64000
                      }
                    },
                    "required": [
                      "method",
                      "url"
                    ]
                  },
                  "response_schema": {},
                  "timeout_ms": {
                    "type": "integer",
                    "minimum": 500,
                    "maximum": 60000,
                    "default": 15000
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  },
                  "volatile": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "required": [
                  "name",
                  "description",
                  "request_template"
                ]
              },
              "example": {
                "name": "consultar_clima",
                "description": "Consulta el clima actual de una ciudad",
                "parameters": [
                  {
                    "name": "ciudad",
                    "type": "string",
                    "description": "Nombre de la ciudad",
                    "required": true
                  }
                ],
                "request_template": {
                  "method": "GET",
                  "url": "https://api.example.com/weather?city=${param.ciudad}"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created tool",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "consultar_clima",
                    "description": "Consulta el clima actual de una ciudad",
                    "kind": "custom_http",
                    "enabled": true,
                    "parameters": [
                      {
                        "name": "ciudad",
                        "type": "string",
                        "description": "Nombre de la ciudad",
                        "required": true
                      }
                    ],
                    "auth_type": "none",
                    "auth_config": {},
                    "request_template": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=${param.ciudad}",
                      "query": [],
                      "headers": [],
                      "body_kind": "none"
                    },
                    "response_schema": null,
                    "sample_response": null,
                    "timeout_ms": 15000,
                    "volatile": false,
                    "created_at": "2026-09-15T12:00:00.000Z",
                    "updated_at": "2026-09-15T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A tool with that name already exists"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/custom-tools/{id}": {
      "get": {
        "summary": "Fetch one custom tool",
        "description": "The full definition including `sample_response` and `response_schema` when they have been captured.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The tool",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "consultar_clima",
                    "description": "Consulta el clima actual de una ciudad",
                    "kind": "custom_http",
                    "enabled": true,
                    "parameters": [
                      {
                        "name": "ciudad",
                        "type": "string",
                        "description": "Nombre de la ciudad",
                        "required": true
                      }
                    ],
                    "auth_type": "none",
                    "auth_config": {},
                    "request_template": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=${param.ciudad}",
                      "query": [],
                      "headers": [],
                      "body_kind": "none"
                    },
                    "response_schema": null,
                    "sample_response": null,
                    "timeout_ms": 15000,
                    "volatile": false,
                    "created_at": "2026-09-15T12:00:00.000Z",
                    "updated_at": "2026-09-15T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Replace a custom tool",
        "description": "A true REPLACE, and the difference from `PATCH` is easy to get wrong. This endpoint validates against the CREATE schema, so every field you omit is filled with its default and written — omitting `parameters` empties the parameter list, omitting `timeout_ms` resets it to 15.000, and omitting `enabled` re-enables the tool.\n\n**Omitting `auth_type` sets it to `none` and clears `auth_config`, so the tool silently stops authenticating** and starts calling the upstream API unauthenticated. Send the complete object, or use `PATCH` for partial edits.\n\nThe one field that does NOT reset is `response_schema`, which has no default and is left alone when absent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 64,
                    "pattern": "^[a-z][a-z0-9_]{1,63}$"
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "parameters": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,63}$"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "integer",
                            "number",
                            "boolean"
                          ]
                        },
                        "description": {
                          "type": "string",
                          "maxLength": 500,
                          "default": ""
                        },
                        "required": {
                          "type": "boolean",
                          "default": false
                        }
                      },
                      "required": [
                        "name",
                        "type"
                      ]
                    },
                    "maxItems": 40,
                    "default": []
                  },
                  "auth_type": {
                    "type": "string",
                    "enum": [
                      "none",
                      "api_key",
                      "bearer",
                      "basic",
                      "oauth2",
                      "static_header"
                    ],
                    "default": "none"
                  },
                  "auth_config": {
                    "type": "object",
                    "properties": {
                      "credential_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "placement": {
                        "type": "string",
                        "enum": [
                          "header",
                          "query",
                          "cookie"
                        ],
                        "default": "header"
                      },
                      "header_name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "query_param": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "prefix": {
                        "type": "string",
                        "maxLength": 40
                      }
                    },
                    "default": {}
                  },
                  "request_template": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "GET",
                          "POST",
                          "PUT",
                          "PATCH",
                          "DELETE"
                        ]
                      },
                      "url": {
                        "type": "string",
                        "minLength": 8,
                        "maxLength": 2048
                      },
                      "headers": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "query": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "body_kind": {
                        "type": "string",
                        "enum": [
                          "none",
                          "json",
                          "form",
                          "raw"
                        ],
                        "default": "none"
                      },
                      "body": {
                        "type": "string",
                        "maxLength": 64000
                      }
                    },
                    "required": [
                      "method",
                      "url"
                    ]
                  },
                  "response_schema": {},
                  "timeout_ms": {
                    "type": "integer",
                    "minimum": 500,
                    "maximum": 60000,
                    "default": 15000
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  },
                  "volatile": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "required": [
                  "name",
                  "description",
                  "request_template"
                ]
              },
              "example": {
                "name": "consultar_clima",
                "description": "Consulta el clima actual de una ciudad",
                "parameters": [
                  {
                    "name": "ciudad",
                    "type": "string",
                    "description": "Nombre de la ciudad",
                    "required": true
                  }
                ],
                "request_template": {
                  "method": "GET",
                  "url": "https://api.example.com/weather?city=${param.ciudad}"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The replaced tool",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "consultar_clima",
                    "description": "Consulta el clima actual de una ciudad",
                    "kind": "custom_http",
                    "enabled": true,
                    "parameters": [
                      {
                        "name": "ciudad",
                        "type": "string",
                        "description": "Nombre de la ciudad",
                        "required": true
                      }
                    ],
                    "auth_type": "none",
                    "auth_config": {},
                    "request_template": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=${param.ciudad}",
                      "query": [],
                      "headers": [],
                      "body_kind": "none"
                    },
                    "response_schema": null,
                    "sample_response": null,
                    "timeout_ms": 15000,
                    "volatile": false,
                    "created_at": "2026-09-15T12:00:00.000Z",
                    "updated_at": "2026-09-15T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a custom tool",
        "description": "Removes the tool and its parameters. Past `tool_invocation` audit rows survive — the record of what was called stays even when the tool itself is gone.\n\nAgents wired to the tool lose it. Setting `enabled: false` is the reversible way to take a tool out of service.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a custom tool",
        "description": "Applies only the fields present and leaves everything else as it is — this is the safe way to change one thing. Prefer it over `PUT` unless you genuinely hold the whole object.\n\n`parameters` and `request_template` are each replaced wholesale when sent, never merged, so a parameter list must be sent complete. Any `auth_type` other than `none` requires `auth_config.credential_id` pointing at a stored credential (`/tool-credentials`). Secrets are never written inline here — the template references them as `${secret.NAME}` and the runtime substitutes them.\n\nChanges take effect on the agent’s next call; nothing needs republishing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 64,
                    "pattern": "^[a-z][a-z0-9_]{1,63}$"
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "parameters": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "pattern": "^[a-zA-Z_][a-zA-Z0-9_]{0,63}$"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "string",
                            "integer",
                            "number",
                            "boolean"
                          ]
                        },
                        "description": {
                          "type": "string",
                          "maxLength": 500,
                          "default": ""
                        },
                        "required": {
                          "type": "boolean",
                          "default": false
                        }
                      },
                      "required": [
                        "name",
                        "type"
                      ]
                    },
                    "maxItems": 40,
                    "default": []
                  },
                  "auth_type": {
                    "type": "string",
                    "enum": [
                      "none",
                      "api_key",
                      "bearer",
                      "basic",
                      "oauth2",
                      "static_header"
                    ],
                    "default": "none"
                  },
                  "auth_config": {
                    "type": "object",
                    "properties": {
                      "credential_id": {
                        "type": "string",
                        "format": "uuid"
                      },
                      "placement": {
                        "type": "string",
                        "enum": [
                          "header",
                          "query",
                          "cookie"
                        ],
                        "default": "header"
                      },
                      "header_name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "query_param": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 120
                      },
                      "prefix": {
                        "type": "string",
                        "maxLength": 40
                      }
                    },
                    "default": {}
                  },
                  "request_template": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "GET",
                          "POST",
                          "PUT",
                          "PATCH",
                          "DELETE"
                        ]
                      },
                      "url": {
                        "type": "string",
                        "minLength": 8,
                        "maxLength": 2048
                      },
                      "headers": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "query": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "name": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 120
                            },
                            "value": {
                              "type": "string",
                              "maxLength": 2048
                            }
                          },
                          "required": [
                            "name",
                            "value"
                          ]
                        },
                        "maxItems": 40,
                        "default": []
                      },
                      "body_kind": {
                        "type": "string",
                        "enum": [
                          "none",
                          "json",
                          "form",
                          "raw"
                        ],
                        "default": "none"
                      },
                      "body": {
                        "type": "string",
                        "maxLength": 64000
                      }
                    },
                    "required": [
                      "method",
                      "url"
                    ]
                  },
                  "response_schema": {},
                  "timeout_ms": {
                    "type": "integer",
                    "minimum": 500,
                    "maximum": 60000,
                    "default": 15000
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  },
                  "volatile": {
                    "type": "boolean",
                    "default": false
                  }
                }
              },
              "example": {
                "description": "Consulta el clima actual (°C) de una ciudad chilena"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated tool",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "consultar_clima",
                    "description": "Consulta el clima actual (°C) de una ciudad chilena",
                    "kind": "custom_http",
                    "enabled": true,
                    "parameters": [
                      {
                        "name": "ciudad",
                        "type": "string",
                        "description": "Nombre de la ciudad",
                        "required": true
                      }
                    ],
                    "auth_type": "none",
                    "auth_config": {},
                    "request_template": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=${param.ciudad}",
                      "query": [],
                      "headers": [],
                      "body_kind": "none"
                    },
                    "response_schema": null,
                    "sample_response": null,
                    "timeout_ms": 15000,
                    "volatile": false,
                    "created_at": "2026-09-15T12:00:00.000Z",
                    "updated_at": "2026-09-15T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/custom-tools/{id}/test": {
      "post": {
        "summary": "Invoke the tool with sample arguments",
        "description": "**This performs the real request.** It is a tester, not a dry run: the configured URL is called with the real credential through the same runtime the agent uses, so testing a tool whose template is a `POST` or `DELETE` creates or destroys data on the upstream system exactly as a live call would. There is no sandbox mode.\n\nAnswers **200 with the outcome**, pass or fail: `{ ok, status, latency_ms, body, body_truncated, error, redacted_request, schema_valid, invocation_id }`. **Read `ok`, not the HTTP status** — a timeout, a refused SSRF target or a 500 from the upstream all come back as a 200 here with `ok: false`.\n\n`ok` is true only for a 2xx with no transport error. Redirects are not followed, so a 301 is reported as-is and counts as a failure. `schema_valid` is null when the tool has no `response_schema`.\n\n`redacted_request` shows exactly what was sent with credential-derived values replaced — the right thing to show an author debugging a template, and safe to display. The same snapshot is what lands in the audit row, tagged `triggered_by: \"tester\"`.\n\n`save_sample: true` stores the response as `sample_response` for `/infer-schema`, but **only when the call succeeded** — a failed test never overwrites a good sample. Requires `functions:write`, because it can write both the sample and the upstream system.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sample_args": {
                    "type": "object",
                    "additionalProperties": {},
                    "default": {}
                  },
                  "save_sample": {
                    "type": "boolean",
                    "default": false
                  }
                }
              },
              "example": {
                "arguments": {
                  "ciudad": "Santiago"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The invocation result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "ok": true,
                    "status": 200,
                    "latency_ms": 214,
                    "body": {
                      "ciudad": "Santiago",
                      "temp_c": 18,
                      "condicion": "despejado"
                    },
                    "body_truncated": false,
                    "error": null,
                    "redacted_request": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=Santiago"
                    },
                    "schema_valid": null,
                    "invocation_id": "a7a7a7a7-3000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/custom-tools/{id}/infer-schema": {
      "post": {
        "summary": "Derive a response schema from the saved sample",
        "description": "Generates a JSON Schema from the stored `sample_response`, saves it as the tool’s `response_schema`, and returns the updated tool. Takes no body — it works from what is already stored, so run `/{id}/test` with `save_sample: true` first. Without a sample it is a 400 saying so.\n\nInference sees ONE response. A field that happened to be null, or an array that happened to be empty, is typed from that single example, so the result is a starting point to edit rather than a finished contract.\n\nThe schema is used to set `schema_valid` on future invocations. It REPORTS a mismatch; it does not reject the response or stop the agent from using it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Custom Tools"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The tool with its inferred response schema",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "consultar_clima",
                    "description": "Consulta el clima actual de una ciudad",
                    "kind": "custom_http",
                    "enabled": true,
                    "parameters": [
                      {
                        "name": "ciudad",
                        "type": "string",
                        "description": "Nombre de la ciudad",
                        "required": true
                      }
                    ],
                    "auth_type": "none",
                    "auth_config": {},
                    "request_template": {
                      "method": "GET",
                      "url": "https://api.example.com/weather?city=${param.ciudad}",
                      "query": [],
                      "headers": [],
                      "body_kind": "none"
                    },
                    "response_schema": {
                      "type": "object",
                      "properties": {
                        "ciudad": {
                          "type": "string"
                        },
                        "temp_c": {
                          "type": "number"
                        },
                        "condicion": {
                          "type": "string"
                        }
                      }
                    },
                    "sample_response": null,
                    "timeout_ms": 15000,
                    "volatile": false,
                    "created_at": "2026-09-15T12:00:00.000Z",
                    "updated_at": "2026-09-15T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/copilot/email-voice": {
      "get": {
        "summary": "The workspace's email voice — read it before writing an email",
        "description": "Everything an AI needs to write an email that sounds like THIS workspace, in one read and with no model call: the workspace language (`locale`), its written style guide, the persona of the AI agent that serves its email (`agent_instructions` — take its tone, ignore its channel mechanics), up to five real emails people of the workspace recently sent (quoted history and signatures stripped, each capped at ~1,200 characters), the caller's default signature, and — with `contact_id` — the recipient.\n\n**If you are a user's own assistant connected through the Vitrina MCP, call this before drafting an email**, write in `locale`, imitate the sample emails (never copy their facts), then save the result with `POST /email-drafts` for a person to review and send. Leave the signature out of the body: it is appended when the person sends.\n\nThe voice is the WORKSPACE's, not a member's: most human replies are typed straight into the connected mailbox, so the samples are the team's mail, not the caller's.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Copilot"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace email voice",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "es",
                    "style_guide": "Cercano y claro. Tuteamos. Frases cortas. Nunca prometemos plazos que no controlamos.",
                    "agent_instructions": "Eres el asistente del equipo. Respondes con calidez y precisión, tuteas y cierras siempre con un próximo paso concreto.",
                    "agent": {
                      "id": "33333333-0000-4000-8000-000000000001",
                      "name": "Asistente"
                    },
                    "sample_emails": [
                      {
                        "subject": "Tu cotización",
                        "body_text": "Hola Pedro,\n\nTe adjunto la cotización que conversamos. Cualquier duda me escribes por aquí o al WhatsApp.\n\nUn abrazo,",
                        "sent_at": "2026-09-20T13:40:00.000Z"
                      }
                    ],
                    "signature": {
                      "id": "44444444-0000-4000-8000-000000000001",
                      "name": "Firma principal",
                      "text": "Persona del equipo\nEquipo comercial"
                    },
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/email-drafts": {
      "get": {
        "summary": "List email drafts",
        "description": "Drafts newest first (by last edit). `mine=true` (the default) lists the caller's own; `mine=false` every draft of the workspace — except for a member whose role narrows record visibility, who always sees only their own. Keyset-paginated: pass `next_cursor` back as `cursor`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "parameters": [
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              ],
              "default": true
            },
            "required": false,
            "name": "mine",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 512
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of drafts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "5d5d5d5d-0000-4000-8000-000000000001",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "María González",
                        "email": "maria.gonzalez@example.cl"
                      },
                      "conversation_id": null,
                      "to_address": "maria.gonzalez@example.cl",
                      "cc": [],
                      "subject": "Resumen de lo que conversamos hoy",
                      "body_html": "<p>Hola María,</p><p>Te dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].</p><p>Saludos,</p>",
                      "body_text": "Hola María,\n\nTe dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].\n\nSaludos,",
                      "signature_id": null,
                      "origin": "mcp",
                      "created_by": {
                        "id": "11111111-0000-4000-8000-000000000001",
                        "name": "Persona del equipo"
                      },
                      "created_at": "2026-09-24T14:02:11.000Z",
                      "updated_at": "2026-09-24T14:05:40.000Z"
                    }
                  ],
                  "next_cursor": null,
                  "meta": {
                    "pagination": {
                      "nextCursor": null,
                      "limit": 20
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Save an email draft for a person to review and send",
        "description": "Prepares a new email WITHOUT sending it. This is how an assistant connected through the Vitrina MCP hands an email to a person: read `GET /copilot/email-voice` first so it sounds like the workspace, save it here, and a person reviews and sends it from Vitrina. `origin` is set by the server from the credential — `mcp` for an API key, personal token or connected app, `human` for a signed-in member.\n\nEvery field is optional — a draft is work in progress, so an empty subject, an empty body or no recipient yet are all storable; what is SENDABLE is decided by `POST /email-drafts/{id}/send`. Recipient: `contact_id` (wins when both are set) or `to_address`. `body_text` is the plain text (≤8,000 characters) and what is sent as the text part; `body_html` the rich version, optional. `signature_id`: one of the sender's saved signatures, `\"none\"` for no signature, or `null` for the sender's default at send time. Never put a signature in the body.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "to_address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "format": "email"
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 255,
                      "format": "email"
                    },
                    "maxItems": 20
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "body_html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "body_text": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "signature_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "enum": [
                          "none"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "from_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "contact_id": "22222222-0000-4000-8000-000000000001",
                "subject": "Resumen de lo que conversamos hoy",
                "body_text": "Hola María,\n\nTe dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].\n\nSaludos,"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The saved draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl"
                    },
                    "conversation_id": null,
                    "to_address": "maria.gonzalez@example.cl",
                    "cc": [],
                    "subject": "Resumen de lo que conversamos hoy",
                    "body_html": "<p>Hola María,</p><p>Te dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].</p><p>Saludos,</p>",
                    "body_text": "Hola María,\n\nTe dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].\n\nSaludos,",
                    "signature_id": null,
                    "origin": "mcp",
                    "created_by": {
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": "Persona del equipo"
                    },
                    "created_at": "2026-09-24T14:02:11.000Z",
                    "updated_at": "2026-09-24T14:05:40.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/email-drafts/{id}": {
      "get": {
        "summary": "Get an email draft",
        "description": "One draft. A draft a narrowed member may not see answers 404, the same as one that does not exist.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl"
                    },
                    "conversation_id": null,
                    "to_address": "maria.gonzalez@example.cl",
                    "cc": [],
                    "subject": "Resumen de lo que conversamos hoy",
                    "body_html": "<p>Hola María,</p><p>Te dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].</p><p>Saludos,</p>",
                    "body_text": "Hola María,\n\nTe dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].\n\nSaludos,",
                    "signature_id": null,
                    "origin": "mcp",
                    "created_by": {
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": "Persona del equipo"
                    },
                    "created_at": "2026-09-24T14:02:11.000Z",
                    "updated_at": "2026-09-24T14:05:40.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard an email draft",
        "description": "The draft is gone; there is no undo.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit an email draft",
        "description": "Partial update: only the fields sent change.\n\nEvery field is optional — a draft is work in progress, so an empty subject, an empty body or no recipient yet are all storable; what is SENDABLE is decided by `POST /email-drafts/{id}/send`. Recipient: `contact_id` (wins when both are set) or `to_address`. `body_text` is the plain text (≤8,000 characters) and what is sent as the text part; `body_html` the rich version, optional. `signature_id`: one of the sender's saved signatures, `\"none\"` for no signature, or `null` for the sender's default at send time. Never put a signature in the body.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "to_address": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255,
                    "format": "email"
                  },
                  "cc": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 255,
                      "format": "email"
                    },
                    "maxItems": 20
                  },
                  "subject": {
                    "type": "string",
                    "maxLength": 255
                  },
                  "body_html": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "body_text": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "signature_id": {
                    "anyOf": [
                      {
                        "type": "string",
                        "format": "uuid"
                      },
                      {
                        "type": "string",
                        "enum": [
                          "none"
                        ]
                      },
                      {
                        "type": "null"
                      }
                    ]
                  },
                  "from_account_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "subject": "Resumen de nuestra reunión",
                "cc": [
                  "operaciones@example.cl"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "5d5d5d5d-0000-4000-8000-000000000001",
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "name": "María González",
                      "email": "maria.gonzalez@example.cl"
                    },
                    "conversation_id": null,
                    "to_address": "maria.gonzalez@example.cl",
                    "cc": [],
                    "subject": "Resumen de lo que conversamos hoy",
                    "body_html": "<p>Hola María,</p><p>Te dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].</p><p>Saludos,</p>",
                    "body_text": "Hola María,\n\nTe dejo por escrito lo que revisamos hoy. Quedamos en coordinar la próxima reunión para el [fecha].\n\nSaludos,",
                    "signature_id": null,
                    "origin": "mcp",
                    "created_by": {
                      "id": "11111111-0000-4000-8000-000000000001",
                      "name": "Persona del equipo"
                    },
                    "created_at": "2026-09-24T14:02:11.000Z",
                    "updated_at": "2026-09-24T14:05:40.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/email-drafts/{id}/send": {
      "post": {
        "summary": "Send an email draft",
        "description": "Sends the draft exactly as `POST /conversations/email` would — same sending mailbox resolution (refused 422 `EMAIL_SENDER_UNRESOLVED` when none resolves), a new thread under HUMAN control assigned to the sender, the sender's signature appended, no unsubscribe footer — and returns the same response. The draft is deleted once the email has left; a send that fails leaves it in place. A draft that is not sendable yet (no recipient, empty subject or body) is refused 422 with `error.field_errors` naming the draft fields. Needs `messages:send`: this is the moment a message leaves Vitrina, and it must be a signed-in person or a token acting as one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Drafts"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The originated conversation, its first message, and the resolved recipient Contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "conversation": {
                      "id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "display_seq": 132,
                      "display_id": "C-132",
                      "channel": "email",
                      "external_id": "web:visitor-9f2c1a",
                      "messaging_account_id": "16161616-0000-4000-8000-000000000001",
                      "brand": null,
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "ticket_id": "dddddddd-0000-4000-8000-000000000001",
                      "status": "open",
                      "handler": "human",
                      "assignee_user_id": "11111111-0000-4000-8000-000000000001",
                      "assignee_assigned_at": "2026-09-22T11:06:54.183Z",
                      "snoozed_until": null,
                      "resolved_at": null,
                      "resolved_by": null,
                      "closed_at": null,
                      "close_deferred_until": null,
                      "transfer_immediately": false,
                      "filtered_at": null,
                      "filtered_reason": null,
                      "summary": null,
                      "summarized_at": null,
                      "history_summary": null,
                      "history_summary_upto_message_id": null,
                      "first_billable_at": null,
                      "billable": null,
                      "billing_reason": null,
                      "assigned_unanswered_alerted_at": null,
                      "assigned_unanswered_realerted_at": null,
                      "bot_gate_override_at": null,
                      "bot_gate_override_by": null,
                      "source": null,
                      "awaiting_human_since": null,
                      "awaiting_payment_since": null,
                      "awaiting_payment_escalated_at": null,
                      "awaiting_obligation_id": null,
                      "ad_referral": null,
                      "ad_join": null,
                      "ad_welcome": null,
                      "metadata": null,
                      "ai_control_source": "legacy",
                      "ai_control_changed_at": null,
                      "ai_control_revision": 0,
                      "ai_control_window_start": null,
                      "ai_control_window_end": null,
                      "ai_control_account_revision": null,
                      "ai_blocked_until": null,
                      "ai_keep_with_human": false,
                      "external_id_provisional": false,
                      "originated_at": null,
                      "originated_by_kind": null,
                      "originated_by_ai_agent_id": null,
                      "originated_by_user_id": null,
                      "originated_outbound_action_id": null,
                      "last_message_date": "2026-09-22T11:08:10.882Z",
                      "created_at": "2026-09-22T11:06:33.744Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "message": {
                      "id": "eeeeeeee-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                      "content": "Hola Rodrigo, te confirmo la visita del jueves a las 10:00.",
                      "type": "text",
                      "sender_role": "assistant",
                      "sender_type": "api_key",
                      "sender_id": "c1c1c1c1-0000-4000-8000-000000000001",
                      "correlation_id": null,
                      "external_message_id": null,
                      "media_urls": null,
                      "media_sensitivity": null,
                      "media_processing_state": null,
                      "media_expires_at": null,
                      "derived_context_expires_at": null,
                      "metadata": null,
                      "tool_calls": null,
                      "tool_call_id": null,
                      "delivery_status": "sent",
                      "delivery_error": null,
                      "delivery_attempt": 1,
                      "next_attempt_at": null,
                      "delivered_at": null,
                      "read_at": null,
                      "author": {
                        "kind": "api_key",
                        "id": "c1c1c1c1-0000-4000-8000-000000000001",
                        "name": "CRM propio"
                      },
                      "created_at": "2026-09-22T11:08:10.882Z",
                      "updated_at": "2026-09-22T11:08:10.882Z"
                    },
                    "contact": {
                      "id": "22222222-0000-4000-8000-000000000001",
                      "created": false,
                      "email": "maria.gonzalez@example.cl"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Not sendable yet (`VALIDATION_ERROR`, with `field_errors`) or no sending mailbox resolves (`EMAIL_SENDER_UNRESOLVED`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/email-templates": {
      "get": {
        "summary": "List saved templates",
        "description": "Every template in the workspace, each with its full `design` blob and a `total`. Unpaginated, and the designs are not small — fetch one by id when you only need one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "responses": {
          "200": {
            "description": "Saved email templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000e7",
                      "name": "Newsletter mensual",
                      "design": {
                        "blocks": [
                          {
                            "type": "heading",
                            "text": "Novedades de octubre"
                          },
                          {
                            "type": "text",
                            "text": "Hola {{first_name}}, estas son las novedades del mes."
                          },
                          {
                            "type": "button",
                            "label": "Ver más",
                            "url": "https://example.cl/novedades"
                          }
                        ],
                        "style": {
                          "background": "#ffffff",
                          "accent": "#0f766e"
                        }
                      },
                      "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                      "created_at": "2026-09-10T09:00:00.000Z",
                      "updated_at": "2026-09-10T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Save a template",
        "description": "`design` is stored **verbatim and unvalidated** beyond being a JSON object. Anything the builder can produce is accepted, and so is anything it cannot — a malformed design saves cleanly here and fails later, wherever it is compiled.\n\nNames are not unique; two templates may share one. `created_by` is the signed-in teammate and is null for an `sk_*` API key. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "design": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "name",
                  "design"
                ]
              },
              "example": {
                "name": "Newsletter mensual",
                "design": {
                  "blocks": [
                    {
                      "type": "heading",
                      "text": "Novedades de octubre"
                    },
                    {
                      "type": "text",
                      "text": "Hola {{first_name}}, estas son las novedades del mes."
                    },
                    {
                      "type": "button",
                      "label": "Ver más",
                      "url": "https://example.cl/novedades"
                    }
                  ],
                  "style": {
                    "background": "#ffffff",
                    "accent": "#0f766e"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The saved template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000e7",
                    "name": "Newsletter mensual",
                    "design": {
                      "blocks": [
                        {
                          "type": "heading",
                          "text": "Novedades de octubre"
                        },
                        {
                          "type": "text",
                          "text": "Hola {{first_name}}, estas son las novedades del mes."
                        },
                        {
                          "type": "button",
                          "label": "Ver más",
                          "url": "https://example.cl/novedades"
                        }
                      ],
                      "style": {
                        "background": "#ffffff",
                        "accent": "#0f766e"
                      }
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-10T09:00:00.000Z",
                    "updated_at": "2026-09-10T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/email-templates/{id}": {
      "get": {
        "summary": "Fetch one template",
        "description": "The template with its design blob.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000e7",
                    "name": "Newsletter mensual",
                    "design": {
                      "blocks": [
                        {
                          "type": "heading",
                          "text": "Novedades de octubre"
                        },
                        {
                          "type": "text",
                          "text": "Hola {{first_name}}, estas son las novedades del mes."
                        },
                        {
                          "type": "button",
                          "label": "Ver más",
                          "url": "https://example.cl/novedades"
                        }
                      ],
                      "style": {
                        "background": "#ffffff",
                        "accent": "#0f766e"
                      }
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-10T09:00:00.000Z",
                    "updated_at": "2026-09-10T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a template",
        "description": "Removes the template. Campaigns built from it are untouched — they copied the design rather than referencing it — so deleting a template never breaks anything already scheduled or sent. A 404 for an id that is not there, so the delete is confirmed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a template",
        "description": "Applies `name`, `design` or both; at least one is required. `design` is REPLACED wholesale — there is no partial edit of a design, so send the whole blob.\n\nThere is no version history. The previous design is gone once this returns, so duplicate first if it might be wanted again. Campaigns already built from this template keep their own copy and are unaffected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "design": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                }
              },
              "example": {
                "name": "Newsletter mensual (octubre)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000e7",
                    "name": "Newsletter mensual (octubre)",
                    "design": {
                      "blocks": [
                        {
                          "type": "heading",
                          "text": "Novedades de octubre"
                        },
                        {
                          "type": "text",
                          "text": "Hola {{first_name}}, estas son las novedades del mes."
                        },
                        {
                          "type": "button",
                          "label": "Ver más",
                          "url": "https://example.cl/novedades"
                        }
                      ],
                      "style": {
                        "background": "#ffffff",
                        "accent": "#0f766e"
                      }
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-09-10T09:00:00.000Z",
                    "updated_at": "2026-10-01T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/email-templates/{id}/duplicate": {
      "post": {
        "summary": "Duplicate a template",
        "description": "Copies the design into a new template named `\"<original> (copia)\"` and answers **201** with it. Takes no body — the name is not yours to choose here, so `PATCH` the copy afterwards to rename it.\n\nDuplicating twice produces two templates with the SAME name, and a copy of a copy is named `\"… (copia) (copia)\"`. The new template is attributed to whoever duplicated it, not to the original author.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Email Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The duplicated template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000e8",
                    "name": "Newsletter mensual (copia)",
                    "design": {
                      "blocks": [
                        {
                          "type": "heading",
                          "text": "Novedades de octubre"
                        },
                        {
                          "type": "text",
                          "text": "Hola {{first_name}}, estas son las novedades del mes."
                        },
                        {
                          "type": "button",
                          "label": "Ver más",
                          "url": "https://example.cl/novedades"
                        }
                      ],
                      "style": {
                        "background": "#ffffff",
                        "accent": "#0f766e"
                      }
                    },
                    "created_by": "7a7a7a7a-0000-4000-8000-000000000011",
                    "created_at": "2026-10-01T14:05:00.000Z",
                    "updated_at": "2026-10-01T14:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups": {
      "get": {
        "summary": "List follow-up recipes",
        "description": "Each row carries two figures and its `origin`. `waiting_count` counts WANTS — subscriptions still open, which for a stock or price recipe means customers still waiting, since a Búsqueda stays open across notices (ADR 0109). `kept_count` counts NOTICES — a timer promise is kept once, when it fires; a Búsqueda is kept once per car it told the customer about. `origin`: `platform` marks a recipe Vitrina created for the workspace (the per-vertical defaults, and the one “Cubrir con Vitrina” mints), `user` one somebody here wrote. Both are editable and deletable.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Follow-up recipes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000010",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "name": "Avisar cuando llegue el auto que busca",
                      "description": "Ofrécelo cuando el cliente pide un auto que no está en stock. Pregunta marca, modelo y presupuesto antes de suscribirlo.",
                      "trigger_key": "vehicle_in_stock",
                      "active": true,
                      "origin": "platform",
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-01T14:00:00.000Z",
                      "waiting_count": 3,
                      "kept_count": 5
                    },
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000011",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "name": "Retomar la cotización en dos días",
                      "description": "Ofrécelo cuando el cliente pide tiempo para decidir. Fija la hora a la que quiere que lo retomen.",
                      "trigger_key": "after_delay",
                      "active": false,
                      "origin": "user",
                      "created_at": "2026-10-03T10:00:00.000Z",
                      "updated_at": "2026-10-03T10:00:00.000Z",
                      "waiting_count": 0,
                      "kept_count": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a follow-up recipe",
        "description": "Creating a recipe schedules nothing on its own — it defines what should happen when `trigger_key` fires. Contacts start receiving it once they have subscriptions.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "trigger_key": {
                    "type": "string",
                    "enum": [
                      "vehicle_in_stock",
                      "vehicle_price_drop",
                      "appointment_slot_available",
                      "vehicle_unavailable",
                      "payment_received",
                      "payment_partial",
                      "obligation_paid",
                      "payment_overdue",
                      "pack_sessions_low",
                      "pack_expiring",
                      "birthday",
                      "booking_abandoned",
                      "after_delay"
                    ]
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "description",
                  "trigger_key"
                ]
              },
              "example": {
                "name": "Retomar la cotización en dos días",
                "description": "Ofrécelo cuando el cliente pide tiempo para decidir. Fija la hora a la que quiere que lo retomen.",
                "trigger_key": "after_delay",
                "active": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created recipe",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000011",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "name": "Retomar la cotización en dos días",
                    "description": "Ofrécelo cuando el cliente pide tiempo para decidir. Fija la hora a la que quiere que lo retomen.",
                    "trigger_key": "after_delay",
                    "active": true,
                    "origin": "user",
                    "created_at": "2026-10-10T14:00:00.000Z",
                    "updated_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/overview": {
      "get": {
        "summary": "Recipes plus channel readiness for the settings screen",
        "description": "The list joined with per-channel `enabled` **and** `connected` state. The two differ: a channel can be enabled for follow-ups while no messaging account can actually deliver on it, which is what makes a recipe silently do nothing.\n\nEach recipe additionally carries `origin` (`platform` = created by Vitrina — a per-vertical default or the one “Cubrir con Vitrina” mints — `user` = written here) and `template`, what a closed-window WhatsApp delivery of THAT recipe would do today: `ready` · `pending` (a managed draft is with Meta) · `gave_up` (Meta refused it enough times; author one in Canales) · `missing` (none yet — Vitrina asks for one at the first fire) · `not_needed` (no WhatsApp account, so there is nothing to want). It is per TRIGGER, so two recipes on the same trigger always agree and two on different triggers need not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Recipes (with `origin` + per-trigger `template`), channel state and workspace template readiness",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "followups": [
                      {
                        "id": "7a7a7a7a-0000-4000-8000-000000000010",
                        "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                        "name": "Avisar cuando llegue el auto que busca",
                        "description": "Ofrécelo cuando el cliente pide un auto que no está en stock. Pregunta marca, modelo y presupuesto antes de suscribirlo.",
                        "trigger_key": "vehicle_in_stock",
                        "active": true,
                        "origin": "platform",
                        "created_at": "2026-10-01T14:00:00.000Z",
                        "updated_at": "2026-10-01T14:00:00.000Z",
                        "waiting_count": 3,
                        "kept_count": 5,
                        "template": "ready"
                      }
                    ],
                    "channels": {
                      "whatsapp": {
                        "enabled": true,
                        "connected": true
                      },
                      "email": {
                        "enabled": true,
                        "connected": true,
                        "sender": {
                          "status": "ready",
                          "account": {
                            "id": "7a7a7a7a-0000-4000-8000-0000000000d0",
                            "name": "Correo ventas",
                            "address": "ventas@automotora-ejemplo.example.cl",
                            "provider": "gmail"
                          },
                          "candidates": [
                            {
                              "id": "7a7a7a7a-0000-4000-8000-0000000000d0",
                              "name": "Correo ventas",
                              "address": "ventas@automotora-ejemplo.example.cl",
                              "provider": "gmail"
                            }
                          ]
                        }
                      },
                      "sms": {
                        "enabled": false,
                        "connected": false
                      },
                      "voice": {
                        "enabled": false,
                        "connected": false
                      }
                    },
                    "whatsapp_template_ready": true,
                    "template_requests": [
                      {
                        "id": "7a7a7a7a-0000-4000-8000-000000000090",
                        "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                        "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                        "status": "open",
                        "fallback_channel": "email",
                        "request_count": 4,
                        "first_seen": "2026-10-05T12:00:00.000Z",
                        "last_seen": "2026-10-09T17:00:00.000Z",
                        "followup_name": "Avisar cuando llegue el auto que busca"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/channels": {
      "get": {
        "summary": "Which channels follow-ups may use",
        "description": "The workspace toggles merged over the defaults: one boolean per channel (`whatsapp`, `email`, `sms`, `voice`), saying whether follow-ups may use it. This is permission only — whether a channel is actually connected is read from `GET /followups/overview`. `sms` is always false: there is no SMS provider yet. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Channel enablement map",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "whatsapp": true,
                    "email": true,
                    "sms": false,
                    "voice": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Toggle the channels follow-ups may use",
        "description": "Partial patch; at least one channel is required. Setting `sms: true` is a 400 — SMS has no provider, so accepting the toggle would promise delivery that cannot happen. The other three are toggleable.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "whatsapp": {
                    "type": "boolean"
                  },
                  "email": {
                    "type": "boolean"
                  },
                  "sms": {
                    "type": "boolean"
                  },
                  "voice": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "email": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merged channel map",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "whatsapp": true,
                    "email": false,
                    "sms": false,
                    "voice": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/busqueda-tolerance": {
      "get": {
        "summary": "The Búsqueda's soft-bound tolerance (partial match)",
        "description": "How far over the customer’s budget or kilometres a car may be and still notify them as a partial match (ADR 0109 §6), as the PERCENTAGES a settings form reads — `budget_pct`/`km_pct`, always present (defaulted to the platform’s 10 % / 15 % when unset). Not a flag: there is no \"off\", a workspace posture only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "The workspace’s Búsqueda tolerance, as percentages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "budget_pct": 10,
                    "km_pct": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the Búsqueda's soft-bound tolerance",
        "description": "Partial patch; at least one bound is required. Each of `budget_pct` and `km_pct` is independently validated `0..MAX_BUSQUEDA_TOLERANCE_PCT` (50) — a value outside that range is a 400, so a fat-fingered workspace setting cannot silently stop grading Búsquedas the way a malformed one would.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "budget_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 50
                  },
                  "km_pct": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 50
                  }
                }
              },
              "example": {
                "budget_pct": 12
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The merged Búsqueda tolerance, as percentages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "budget_pct": 12,
                    "km_pct": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/demand": {
      "get": {
        "summary": "List unmet demand groups",
        "description": "“Avisos sin cubrir” — one row per GROUP (kind + criteria), with the customers waiting behind it, a human `title`, and a `cover` block saying whether Vitrina can cover the group and whether a WhatsApp template is ready for it. `request_count` is the number of distinct customers.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "planned",
                "resolved",
                "dismissed"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Demand rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000080",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "kind": "vehicle",
                      "status": "open",
                      "criteria": {
                        "make": "Mazda",
                        "model": "CX-5",
                        "year_min": 2020
                      },
                      "raw_ask": "Busca una Mazda CX-5 2020 o más nueva",
                      "normalized_key": "vehicle:mazda:cx-5:2020",
                      "suggested_trigger": "vehicle_in_stock",
                      "request_count": 1,
                      "topic_id": null,
                      "last_contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                      "last_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                      "first_seen": "2026-10-08T15:00:00.000Z",
                      "last_seen": "2026-10-09T18:30:00.000Z",
                      "contacts": [
                        {
                          "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                          "name": "María González",
                          "phone": "+56 9 1234 5678",
                          "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                          "lead_display_id": "L-89",
                          "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                          "first_seen": "2026-10-08T15:00:00.000Z"
                        }
                      ],
                      "title": "Mazda CX-5 · 2020",
                      "cover": {
                        "kind": "followup_subscription",
                        "available": true,
                        "template": "ready"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Log an unmet demand",
        "description": "Recorded when someone describes an ask the business cannot satisfy, so the gap is sized rather than lost. The group is keyed off `kind` + `criteria`, which are validated against that kind’s strict schema; `normalized_key` is required for `kind: other` and ignored otherwise. No contact is attached on this path.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "vehicle",
                      "vehicle_release",
                      "slot",
                      "product",
                      "service_not_offered",
                      "other"
                    ]
                  },
                  "criteria": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "summary": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 500
                  },
                  "normalized_key": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 2,
                    "maxLength": 120
                  }
                },
                "required": [
                  "kind",
                  "criteria",
                  "summary"
                ]
              },
              "example": {
                "kind": "vehicle",
                "criteria": {
                  "make": "Mazda",
                  "model": "CX-5",
                  "year_min": 2020
                },
                "summary": "Busca una Mazda CX-5 2020 o más nueva"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The logged demand row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000080",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "kind": "vehicle",
                    "status": "open",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020
                    },
                    "raw_ask": "Busca una Mazda CX-5 2020 o más nueva",
                    "normalized_key": "vehicle:mazda:cx-5:2020",
                    "suggested_trigger": "vehicle_in_stock",
                    "request_count": 1,
                    "topic_id": null,
                    "last_contact_id": null,
                    "last_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "first_seen": "2026-10-08T15:00:00.000Z",
                    "last_seen": "2026-10-09T18:30:00.000Z",
                    "covered": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/demand/topics": {
      "get": {
        "summary": "List unmet demand by topic",
        "description": "The same demand, grouped by KIND — the level the workspace acts at. One row per kind with `customers` (DISTINCT contacts across its groups, so the same person waiting on two cars counts once), the groups under `demands`, and the group `cover` block. Sorted by `customers` desc. Only `open` and `planned` are topics; the other two statuses are triage history and are read through `GET /followups/demand`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "planned"
              ],
              "default": "open"
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Demand topics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "kind": "vehicle",
                      "status": "open",
                      "customers": 1,
                      "demands": [
                        {
                          "id": "7a7a7a7a-0000-4000-8000-000000000080",
                          "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                          "kind": "vehicle",
                          "status": "open",
                          "criteria": {
                            "make": "Mazda",
                            "model": "CX-5",
                            "year_min": 2020
                          },
                          "raw_ask": "Busca una Mazda CX-5 2020 o más nueva",
                          "normalized_key": "vehicle:mazda:cx-5:2020",
                          "suggested_trigger": "vehicle_in_stock",
                          "request_count": 1,
                          "topic_id": null,
                          "last_contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                          "last_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                          "first_seen": "2026-10-08T15:00:00.000Z",
                          "last_seen": "2026-10-09T18:30:00.000Z",
                          "contacts": [
                            {
                              "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                              "name": "María González",
                              "phone": "+56 9 1234 5678",
                              "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                              "lead_display_id": "L-89",
                              "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                              "first_seen": "2026-10-08T15:00:00.000Z"
                            }
                          ],
                          "title": "Mazda CX-5 · 2020",
                          "cover": {
                            "kind": "followup_subscription",
                            "available": true,
                            "template": "ready"
                          }
                        }
                      ],
                      "cover": {
                        "kind": "followup_subscription",
                        "available": true,
                        "template": "ready"
                      },
                      "last_seen": "2026-10-09T18:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/demand/topics/{kind}/cover": {
      "post": {
        "summary": "Cover a whole demand topic with Vitrina",
        "description": "Covers EVERY open demand of the kind by running the per-group cover on each, so every customer keeps the criteria THEY asked about. 409 when the kind has no cover recipe or nothing of it is open. A group that fails does not abort the pass — it comes back in `demands_left_open`, while `covered`/`skipped` count customers across the whole topic.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "vehicle",
                "vehicle_release",
                "slot",
                "product",
                "service_not_offered",
                "other"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-customer and per-group counts for the topic",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "kind": "vehicle",
                    "covered": 1,
                    "skipped": [],
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "template": {
                      "status": "ready",
                      "name": "aviso_auto_disponible"
                    },
                    "demands_covered": 1,
                    "demands_left_open": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/demand/topics/{kind}": {
      "patch": {
        "summary": "Dismiss every open demand of a topic",
        "description": "`open` rows only, in one statement — a topic already covered keeps its `planned` groups. `dismissed` is the only status a topic accepts, and a kind with nothing open answers `{updated: 0}` rather than 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "vehicle",
                "vehicle_release",
                "slot",
                "product",
                "service_not_offered",
                "other"
              ]
            },
            "required": true,
            "name": "kind",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "dismissed"
                    ]
                  }
                },
                "required": [
                  "status"
                ]
              },
              "example": {
                "status": "dismissed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many demands were dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "updated": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/demand/{id}/cover": {
      "post": {
        "summary": "Cover a demand group with Vitrina",
        "description": "Turns the group into real promises: a `vehicle` group subscribes every customer to the platform `vehicle_in_stock` follow-up (created once per workspace); a `slot` group adds each of them to the clinic waitlist. The demand moves to `planned`. 409 when the kind has no cover recipe or the demand is not open. The response reports who was covered and who was skipped, with a reason each.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The covered demand, with counts and skips",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "demand": {
                      "id": "7a7a7a7a-0000-4000-8000-000000000080",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "kind": "vehicle",
                      "status": "planned",
                      "criteria": {
                        "make": "Mazda",
                        "model": "CX-5",
                        "year_min": 2020
                      },
                      "raw_ask": "Busca una Mazda CX-5 2020 o más nueva",
                      "normalized_key": "vehicle:mazda:cx-5:2020",
                      "suggested_trigger": "vehicle_in_stock",
                      "request_count": 1,
                      "topic_id": null,
                      "last_contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                      "last_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                      "first_seen": "2026-10-08T15:00:00.000Z",
                      "last_seen": "2026-10-09T18:30:00.000Z",
                      "contacts": [
                        {
                          "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                          "name": "María González",
                          "phone": "+56 9 1234 5678",
                          "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                          "lead_display_id": "L-89",
                          "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                          "first_seen": "2026-10-08T15:00:00.000Z"
                        }
                      ]
                    },
                    "covered": 1,
                    "skipped": [],
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "template": {
                      "status": "ready",
                      "name": "aviso_auto_disponible"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/demand/{id}": {
      "patch": {
        "summary": "Triage one demand row",
        "description": "`id` is a demand row, not a followup.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "planned",
                      "resolved",
                      "dismissed"
                    ]
                  }
                },
                "required": [
                  "status"
                ]
              },
              "example": {
                "status": "dismissed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated demand row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000080",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "kind": "vehicle",
                    "status": "dismissed",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020
                    },
                    "raw_ask": "Busca una Mazda CX-5 2020 o más nueva",
                    "normalized_key": "vehicle:mazda:cx-5:2020",
                    "suggested_trigger": "vehicle_in_stock",
                    "request_count": 1,
                    "topic_id": null,
                    "last_contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "last_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "first_seen": "2026-10-08T15:00:00.000Z",
                    "last_seen": "2026-10-09T18:30:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/template-requests": {
      "get": {
        "summary": "List open WhatsApp template requests",
        "description": "Follow-ups outside WhatsApp’s 24-hour window need an approved template. A recipe that wants one it does not have raises a request here. Always returns the `open` ones — there is no status filter.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Open template requests",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000090",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                      "status": "open",
                      "fallback_channel": "email",
                      "request_count": 4,
                      "first_seen": "2026-10-05T12:00:00.000Z",
                      "last_seen": "2026-10-09T17:00:00.000Z",
                      "followup_name": "Avisar cuando llegue el auto que busca"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/template-requests/{id}": {
      "patch": {
        "summary": "Dismiss a template request or mark it created",
        "description": "`id` is a template request. Marking it `created` only clears the nudge — it submits nothing to Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "open",
                      "created",
                      "dismissed"
                    ]
                  }
                },
                "required": [
                  "status"
                ]
              },
              "example": {
                "status": "dismissed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated request",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000090",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "status": "dismissed",
                    "fallback_channel": "email",
                    "request_count": 4,
                    "first_seen": "2026-10-05T12:00:00.000Z",
                    "last_seen": "2026-10-09T17:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto": {
      "get": {
        "summary": "Get the Recontacto cadence configuration",
        "description": "Situations are code (`never_replied`, `stalled`, `lost`, plus the seen-without-reply nudge); everything inside one is editable. A rung waits `after_hours` since the PREVIOUS rung (rung 1: since the silence) and rides one of `whatsapp | instagram | email | voice`; `unit` is the editor’s display preference and the engine never reads it. There is no per-lead cap — the rails are the cap; `daily_cap` is the volume control. `max_evaluations_per_step` bounds how many times a rung may be reconsidered without contacting anyone. `pacing` is «Ritmo» (every pacing rule, off by default except `entry_max_silence_days` 45, `max_paths_per_lead` 3 and `max_messages_per_person_90d` 6): `min_gap_hours` (R2), `max_per_day` (R5; a healthcare workspace reads 1 when none is stored, and null keeps 1), `pause_after_unanswered_days` (R1), `approval_after_silence_days` (R9), `approval_fallback_hours` (R11), `entry_max_silence_days` (R10), `hot_leads_faster` (R6), `max_paths_per_lead` (R12, per person across all their leads and chat rows; only paths written to since the person’s last reply count, so a reply resets it), `max_messages_per_person_90d` (R13: unanswered Recontacto messages one person may get in any 90 days, every rail together). `unanswered_message.template_after_window` is R7 (on). `min_hours_between_touches` is an alias of `pacing.min_gap_hours`. `after_handoff` is «Después de pasarlo a una persona» (A9.2): a conversation is silent from an AI handoff of any kind or a teammate takeover until «Devolver a la IA» or the close. `while_silent` (stored as `followups` + `auto_send_after_hours`): `draft_and_remind` (default: each due step a draft for whoever handles it, sent by hand), `draft_auto_send` (the draft sends itself after `auto_send_after_hours`, 1 to 144, inside contact hours; a teammate’s promised time wins), `remind_only` (no draft; whoever handles it is reminded) or `nothing`. `ai_answers_waiting`: `never` (default: whoever handles it is reminded at 30 minutes, then an admin), `after_minutes` (the AI answers after `ai_answers_waiting_after_minutes`, 5 to 720) or `after_quiet_days` (only a customer back after 7 quiet days). `ai_answers_waiting_after_hours` is the older spelling (N = `after_minutes` with N × 60, null = `never`). `ai_answers_waiting_keep_owner` (default false): with `after_minutes`, the AI answers once per wait only what it knows, or sends one short status line when the open question waits on the team, or nothing; the conversation stays with the person (handler and assignee unchanged). A person whose lead is in a late stage (visit scheduled or done, a sale under way) gets no follow-ups for 14 days after entering it or their last message (G52); then the ladder resumes through the backlog queue.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Recontacto config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enabled": false,
                    "daily_cap": 200,
                    "lost_lookback_days": 90,
                    "max_evaluations_per_step": 3,
                    "re_evaluation_after_days": 1,
                    "min_hours_between_touches": null,
                    "excluded_template_ids": [],
                    "voice_examples": [],
                    "situations": {
                      "never_replied": {
                        "enabled": true,
                        "steps": [
                          {
                            "after_hours": 72,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "auto"
                          }
                        ],
                        "lose_after_days": 7
                      },
                      "stalled": {
                        "enabled": true,
                        "steps": [
                          {
                            "after_hours": 72,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "assignee"
                          },
                          {
                            "after_hours": 168,
                            "unit": "d",
                            "channel": "email",
                            "autonomy": "assignee"
                          },
                          {
                            "after_hours": 336,
                            "unit": "d",
                            "channel": "voice",
                            "autonomy": "approval",
                            "min_score": 60
                          }
                        ],
                        "lose_after_days": 14
                      },
                      "lost": {
                        "enabled": false,
                        "steps": [
                          {
                            "after_hours": 168,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "approval"
                          }
                        ],
                        "lose_after_days": null
                      }
                    },
                    "unanswered_message": {
                      "enabled": false,
                      "autonomy": "assignee",
                      "steps": [
                        {
                          "after_hours": 3
                        },
                        {
                          "after_hours": 6
                        }
                      ],
                      "conversation_lifetime_cap": 6,
                      "template_after_window": true
                    },
                    "pacing": {
                      "min_gap_hours": null,
                      "max_per_day": null,
                      "pause_after_unanswered_days": null,
                      "approval_after_silence_days": null,
                      "approval_fallback_hours": null,
                      "entry_max_silence_days": 45,
                      "hot_leads_faster": false,
                      "max_paths_per_lead": 3,
                      "max_messages_per_person_90d": 6
                    },
                    "after_handoff": {
                      "followups": "draft_for_owner",
                      "auto_send_after_hours": null,
                      "while_silent": "draft_and_remind",
                      "ai_answers_waiting": "never",
                      "ai_answers_waiting_after_minutes": 30,
                      "ai_answers_waiting_after_hours": null,
                      "ai_answers_waiting_keep_owner": false
                    },
                    "last_run_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update the Recontacto cadence configuration",
        "description": "Partial patch. A patch that LOOSENS outbound additionally requires `campaigns:write`: enabling Recontacto or the chat rail; any step set to `auto` or `assignee`; setting `pacing.approval_fallback_hours`; clearing or raising `pacing.max_per_day`, `pacing.min_gap_hours` (or `min_hours_between_touches`), `pacing.pause_after_unanswered_days` or `pacing.approval_after_silence_days`; raising `pacing.max_paths_per_lead`, `pacing.max_messages_per_person_90d` or `pacing.entry_max_silence_days`; turning `unanswered_message.template_after_window` or `pacing.hot_leads_faster` on; `after_handoff` letting the draft send itself where it did not (or sooner), or the AI answer a waiting customer where it did not (`never` → `after_quiet_days` → `after_minutes`, or fewer minutes). Tightening needs only `followups:write`. `min_hours_between_touches` and `pacing.min_gap_hours` are one setting; `pacing.min_gap_hours` wins when both are sent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "daily_cap": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 5000
                  },
                  "daily_review_budget": {
                    "type": "integer",
                    "minimum": 50,
                    "maximum": 5000
                  },
                  "lost_lookback_days": {
                    "type": "integer",
                    "minimum": 7,
                    "maximum": 365
                  },
                  "max_evaluations_per_step": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  },
                  "re_evaluation_after_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90
                  },
                  "min_hours_between_touches": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 720
                  },
                  "excluded_template_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  },
                  "voice_examples": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 280
                    },
                    "maxItems": 8
                  },
                  "situations": {
                    "type": "object",
                    "properties": {
                      "never_replied": {
                        "type": "object",
                        "properties": {
                          "enabled": {
                            "type": "boolean"
                          },
                          "steps": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "after_hours": {
                                  "type": "integer",
                                  "minimum": 1,
                                  "maximum": 8760
                                },
                                "unit": {
                                  "type": "string",
                                  "enum": [
                                    "d",
                                    "h"
                                  ]
                                },
                                "channel": {
                                  "type": "string",
                                  "enum": [
                                    "whatsapp",
                                    "instagram",
                                    "email",
                                    "voice"
                                  ]
                                },
                                "autonomy": {
                                  "type": "string",
                                  "enum": [
                                    "auto",
                                    "assignee",
                                    "approval"
                                  ]
                                },
                                "min_score": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "minimum": 0,
                                  "maximum": 100
                                }
                              },
                              "required": [
                                "after_hours",
                                "channel",
                                "autonomy"
                              ]
                            },
                            "minItems": 1,
                            "maxItems": 5
                          },
                          "lose_after_days": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "minimum": 1,
                            "maximum": 365
                          }
                        }
                      },
                      "stalled": {
                        "type": "object",
                        "properties": {
                          "enabled": {
                            "type": "boolean"
                          },
                          "steps": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "after_hours": {
                                  "type": "integer",
                                  "minimum": 1,
                                  "maximum": 8760
                                },
                                "unit": {
                                  "type": "string",
                                  "enum": [
                                    "d",
                                    "h"
                                  ]
                                },
                                "channel": {
                                  "type": "string",
                                  "enum": [
                                    "whatsapp",
                                    "instagram",
                                    "email",
                                    "voice"
                                  ]
                                },
                                "autonomy": {
                                  "type": "string",
                                  "enum": [
                                    "auto",
                                    "assignee",
                                    "approval"
                                  ]
                                },
                                "min_score": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "minimum": 0,
                                  "maximum": 100
                                }
                              },
                              "required": [
                                "after_hours",
                                "channel",
                                "autonomy"
                              ]
                            },
                            "minItems": 1,
                            "maxItems": 5
                          },
                          "lose_after_days": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "minimum": 1,
                            "maximum": 365
                          }
                        }
                      },
                      "lost": {
                        "type": "object",
                        "properties": {
                          "enabled": {
                            "type": "boolean"
                          },
                          "steps": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "after_hours": {
                                  "type": "integer",
                                  "minimum": 1,
                                  "maximum": 8760
                                },
                                "unit": {
                                  "type": "string",
                                  "enum": [
                                    "d",
                                    "h"
                                  ]
                                },
                                "channel": {
                                  "type": "string",
                                  "enum": [
                                    "whatsapp",
                                    "instagram",
                                    "email",
                                    "voice"
                                  ]
                                },
                                "autonomy": {
                                  "type": "string",
                                  "enum": [
                                    "auto",
                                    "assignee",
                                    "approval"
                                  ]
                                },
                                "min_score": {
                                  "type": [
                                    "integer",
                                    "null"
                                  ],
                                  "minimum": 0,
                                  "maximum": 100
                                }
                              },
                              "required": [
                                "after_hours",
                                "channel",
                                "autonomy"
                              ]
                            },
                            "minItems": 1,
                            "maxItems": 5
                          },
                          "lose_after_days": {
                            "type": [
                              "integer",
                              "null"
                            ],
                            "minimum": 1,
                            "maximum": 365
                          }
                        }
                      }
                    }
                  },
                  "unanswered_message": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean"
                      },
                      "autonomy": {
                        "type": "string",
                        "enum": [
                          "auto",
                          "assignee",
                          "approval"
                        ]
                      },
                      "steps": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "after_hours": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 48
                            }
                          },
                          "required": [
                            "after_hours"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 3
                      },
                      "conversation_lifetime_cap": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 20
                      },
                      "template_after_window": {
                        "type": "boolean"
                      }
                    }
                  },
                  "pacing": {
                    "type": "object",
                    "properties": {
                      "min_gap_hours": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 720
                      },
                      "max_per_day": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 10
                      },
                      "pause_after_unanswered_days": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 90
                      },
                      "approval_after_silence_days": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 365
                      },
                      "approval_fallback_hours": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 168
                      },
                      "entry_max_silence_days": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 365
                      },
                      "hot_leads_faster": {
                        "type": "boolean"
                      },
                      "max_paths_per_lead": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10
                      },
                      "max_messages_per_person_90d": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 50
                      }
                    }
                  },
                  "after_handoff": {
                    "type": "object",
                    "properties": {
                      "while_silent": {
                        "type": "string",
                        "enum": [
                          "draft_and_remind",
                          "draft_auto_send",
                          "remind_only",
                          "nothing"
                        ]
                      },
                      "followups": {
                        "type": "string",
                        "enum": [
                          "draft_for_owner",
                          "remind_only",
                          "off",
                          "automatic"
                        ]
                      },
                      "auto_send_after_hours": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 144
                      },
                      "ai_answers_waiting": {
                        "type": "string",
                        "enum": [
                          "never",
                          "after_minutes",
                          "after_quiet_days"
                        ]
                      },
                      "ai_answers_waiting_after_minutes": {
                        "type": "integer",
                        "minimum": 5,
                        "maximum": 720
                      },
                      "ai_answers_waiting_after_hours": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 1,
                        "maximum": 12
                      },
                      "ai_answers_waiting_keep_owner": {
                        "type": "boolean"
                      }
                    }
                  }
                }
              },
              "example": {
                "daily_cap": 100,
                "pacing": {
                  "min_gap_hours": 24,
                  "max_per_day": 1
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enabled": false,
                    "daily_cap": 100,
                    "lost_lookback_days": 90,
                    "max_evaluations_per_step": 3,
                    "re_evaluation_after_days": 1,
                    "min_hours_between_touches": 24,
                    "excluded_template_ids": [],
                    "voice_examples": [],
                    "situations": {
                      "never_replied": {
                        "enabled": true,
                        "steps": [
                          {
                            "after_hours": 72,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "auto"
                          }
                        ],
                        "lose_after_days": 7
                      },
                      "stalled": {
                        "enabled": true,
                        "steps": [
                          {
                            "after_hours": 72,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "assignee"
                          },
                          {
                            "after_hours": 168,
                            "unit": "d",
                            "channel": "email",
                            "autonomy": "assignee"
                          },
                          {
                            "after_hours": 336,
                            "unit": "d",
                            "channel": "voice",
                            "autonomy": "approval",
                            "min_score": 60
                          }
                        ],
                        "lose_after_days": 14
                      },
                      "lost": {
                        "enabled": false,
                        "steps": [
                          {
                            "after_hours": 168,
                            "unit": "d",
                            "channel": "whatsapp",
                            "autonomy": "approval"
                          }
                        ],
                        "lose_after_days": null
                      }
                    },
                    "unanswered_message": {
                      "enabled": false,
                      "autonomy": "assignee",
                      "steps": [
                        {
                          "after_hours": 3
                        },
                        {
                          "after_hours": 6
                        }
                      ],
                      "conversation_lifetime_cap": 6,
                      "template_after_window": true
                    },
                    "pacing": {
                      "min_gap_hours": 24,
                      "max_per_day": 1,
                      "pause_after_unanswered_days": null,
                      "approval_after_silence_days": null,
                      "approval_fallback_hours": null,
                      "entry_max_silence_days": 45,
                      "hot_leads_faster": false,
                      "max_paths_per_lead": 3,
                      "max_messages_per_person_90d": 6
                    },
                    "after_handoff": {
                      "followups": "draft_for_owner",
                      "auto_send_after_hours": null,
                      "while_silent": "draft_and_remind",
                      "ai_answers_waiting": "never",
                      "ai_answers_waiting_after_minutes": 30,
                      "ai_answers_waiting_after_hours": null,
                      "ai_answers_waiting_keep_owner": false
                    },
                    "last_run_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/preview": {
      "get": {
        "summary": "Preview today’s Recontacto sweep (dry run)",
        "description": "The sweep’s selection with every side effect removed: same query, same decision function, same ordering under the daily cap, and the same send-time guards the sweep applies before composing (one ladder per person, R2, R1, an agreed reminder, WhatsApp undeliverable, the per-person ceiling, opt-outs and no reachable channel): a step one of them stops is a `skip` row, and a chat row it stops carries `skip_reason` and `skip_label`. `truncated` says whether `evaluated` is the query ceiling rather than the whole base. `today.would_need_approval` of `today.due` is the R8 status line: of the steps due now, how many a person handles, i.e. would go to approval under «Automático salvo asignado». `goes_at` is when the rows that go today actually go (now inside contact hours, else the next opening); `limit_reached` names a spent daily limit (`daily_cap`, `review_budget`) and `resumes_at` when deferred rows go. Every lead row and chat row carries its `contact_id`; a `skip` row carries `skip_label`, the guard registry’s words for `skip_reason`, in `lang` (default the workspace’s).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Sweep preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "evaluated": 1,
                    "truncated": false,
                    "total_candidates": 1,
                    "candidates": [
                      {
                        "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                        "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                        "lead_title": "Mazda CX-5 2021",
                        "contact_name": "María González",
                        "contact_phone": "+56 9 1234 5678",
                        "contact_email": "maria.gonzalez@example.cl",
                        "score": 72,
                        "temperature": "warm",
                        "value_amount": 17500000,
                        "situation": "stalled",
                        "action": "attempt",
                        "step": 1,
                        "channel": "whatsapp",
                        "mode": "approval",
                        "skip_reason": null,
                        "skip_label": null,
                        "deferred": false
                      }
                    ],
                    "daily_cap": 200,
                    "sent_today": 0,
                    "tried_today": 0,
                    "automated_today": 0,
                    "unanswered_message": {
                      "enabled": false,
                      "evaluated": 0,
                      "candidates": []
                    },
                    "today": {
                      "would_need_approval": 0,
                      "due": 1
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/guards": {
      "get": {
        "summary": "Rules every follow-up respects, and today’s non-sends",
        "description": "Rendered from the guard registry: `guards` is the «Siempre se respeta» list (one paragraph each, tenant values in `**bold**`, an optional link), `guards_summary` its collapsed line, and `log` today’s non-sends grouped by the guard that stopped them (a code the registry does not know comes back with `guard: null` and a generic text). `held` is «esperando (por qué)»: today’s due steps a sweep held without a ledger row (daily cap, one ladder per person, an agreed reminder, the hour after another automated message…), grouped the same way and counted in leads or threads still waiting; `today.held` totals it. `emphasis: alert` marks a guard that is stopping every message today (no active agent). `lang` picks es or en; default the workspace’s.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Guard report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "es",
                    "guards_summary": "Siempre se respeta · horario de 09:00 a 21:00, no contactar, ventana de WhatsApp y 15 reglas más",
                    "guards": [
                      {
                        "guard": "G1",
                        "text": "Solo escribimos entre **09:00** y **21:00**. Lo que cae fuera sale a las **09:00** siguientes.",
                        "link": null,
                        "shown_as": [
                          "S",
                          "Row"
                        ],
                        "emphasis": "normal",
                        "note": null
                      },
                      {
                        "guard": "G3",
                        "text": "Nunca a quien pidió no recibir mensajes, a contactos silenciados ni a cuentas grandes.",
                        "link": {
                          "label": "No contactar",
                          "href": "/campaigns/suppressions"
                        },
                        "shown_as": [
                          "S",
                          "Log"
                        ],
                        "emphasis": "normal",
                        "note": null
                      }
                    ],
                    "log": [
                      {
                        "guard": "G7",
                        "text": "La IA escribe cada mensaje leyendo la conversación. Si no hay un paso pendiente o el borrador no pasa los controles, no sale y queda anotado por qué.",
                        "count": 5,
                        "codes": [
                          {
                            "code": "no_reason",
                            "count": 3
                          },
                          {
                            "code": "repeated_greeting",
                            "count": 2
                          }
                        ]
                      }
                    ],
                    "today": {
                      "since": "2026-10-22T03:00:00.000Z",
                      "sent": 12,
                      "not_sent": 5
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/held": {
      "get": {
        "summary": "The people behind one «esperando (por qué)» line",
        "description": "Today’s held subjects for the given guard codes (comma-separated, the `codes` of one `/guards` `held` line): each lead or thread a sweep held without a ledger row and that has not been sent since, with its contact, step and when the hold lifts when known (`held_until`), and `step_label`, the step as the «Si no responde» hero names it («Conversó y dejó de responder · paso 3 de 5»; null when the plan does not draw it). Each row also says what holds it, as `/next-steps` does: `guard`, `label` (the owner’s words; for G52 the predicate that matches now, «tiene una visita agendada»), `reason` (G52 only: `visit_scheduled` | `appointment` | `visit_note` | `visited` | `sale_in_progress`, else null), `until` and `until_label` («jue 5 nov») when it lifts on a date, else null, and `until_text` when it lifts on an event instead (G50 «hasta que la devuelvan a la IA o se cierre la conversación», a G52 stage «hasta que la oportunidad cambie de etapa o se cierre»), else null. `lang` picks es or en. One row per person: their latest hold. Same day and same predicate as the `held` counts, newest first, at most 100.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 500,
              "pattern": "^[a-z0-9_]+(,[a-z0-9_]+)*$"
            },
            "required": true,
            "name": "codes",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Held subjects",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "code": "no_reachable_address",
                      "lead_id": "7b0f3c5e-2a51-4d7e-9a61-3f2c8e1d4b90",
                      "conversation_id": null,
                      "contact_id": "01a1095d-6c2e-4f3b-8a7d-5e9b0c1d2f34",
                      "contact_name": "Rocío",
                      "contact_phone": null,
                      "contact_email": null,
                      "lead_title": "iPhone 15 Pro 256 GB",
                      "situation": "never_replied",
                      "step": 1,
                      "last_at": "2026-10-24T14:00:12.000Z",
                      "held_until": null,
                      "step_label": "Nunca respondió nuestro primer mensaje · paso 1 de 4",
                      "guard": "G25",
                      "label": "sin un canal por el que escribirle: sale cuando su ficha tenga teléfono o correo",
                      "reason": null,
                      "until": null,
                      "until_label": null,
                      "until_text": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/plan": {
      "get": {
        "summary": "What a customer who stops answering receives (simulated)",
        "description": "The «Si no responde» ladder, computed by the engine itself: a synthetic lead is stepped along the sweeps’ clocks (hourly for the lead rails, every 15 minutes for the chat rows, only inside contact hours) through the real decision function and pacing holds, so every row’s `at` is when the engine would actually send it. `scenarios` holds `conversed` («Conversó y dejó de responder»: chat rows, then the WhatsApp/email/call rows, both counted from t0), `never_replied` («Nunca respondió nuestro primer mensaje»: t0 is our first message; the lost rail is never reached) and `lost` («Ya está perdido», counted from the loss); `?scenario=` returns just one. `start` is t0 (default now); `start_options` are the example clock’s picks. A moved row carries `deferred_code` (`outside_contact_hours`, `previous_late`, `unanswered_hold`, `min_gap`, `max_per_day`, `automated_gap`, `person_ceiling`, `daily_cap`) with its sentence, and `deferred_codes` lists every reason that moved it, and `hot_at` when R6 moves it for a hot lead; a drawn step the engine never sends has `sends: false` and `not_sent_reason`. Also: `header_sentence`, each scenario’s hero `sentence`, `pacing_summary` (the collapsed «Ritmo» line: the rules that are on) and `pacing` (every «Ritmo» rule R1–R13 with `on` and `value`), `limits_sentence` (every bound on what one person can receive, with the workspace values), row `warnings` for the editor (a first contact under 12 h, approval rows’ real send rate, Instagram past 24 h), `guards` from the registry (G21 `highlight` when a sweep hit its candidate ceiling in the last 24 h), `approval_fallback.available` (false for a clinic whose clinical response times are undeclared; `hours` is R11), `today` (sent, not sent, went to approval, awaiting approval, and the R8 counter `would_need_approval` of `due`, `failed` (follow-ups sent today that did not arrive) and `failed_actionable` (of those, the ones a person can retry or fix; never a number WhatsApp cannot deliver to), and `cap`: what the day used of the daily cap and the AI review budget, the spent one in `reached` with its `sentence` and `resumes_at`; while one is spent, rows at «now» that fall today move to tomorrow with `deferred_code: daily_cap`; and `backlog`, the whole queue’s drain: `queued`, `per_day`, `days` (first to last estimated release, inclusive), `last_release_on` and `sentence` «En cola: N, se reparte en ~D días (hasta el …)», null when nothing is queued) and `queue` (each queued backlog step’s estimated release day, 10 per tenant per day; `contact_id` or `lead_id` narrows it), and `after_handoff` («Después de pasarlo a una persona»: `followups`, `auto_send_after_hours`, `ai_answers_waiting_after_hours`, `reminder_minutes` and the block’s collapsed line `summary`, `controls` (which of `followups`, `auto_send_after_hours`, `ai_answers_waiting_after_hours` do anything here: the AI-answer control is false at a clinic, where D73 and the teammate-reply resume run instead), and `now`, the live state: `conversations` with the team after an AI handoff or a teammate takeover, `by_class` (`informational`, `operational`, `sensitive`, and `takeover` for chats a teammate took without a handoff), `drafts` (waiting in «Por aprobar»: only drafts whose action is still awaiting approval; a rejected one leaves at once), `upcoming_drafts` («Próximos borradores»: people whose next step is a draft not written yet), `drafts_self_sending`, `customers_waiting`, the fact line `sentence` and `people` (one per conversation: `contact_id`, `contact_name`, `conversation_id`, `conversation_display_id`, `owner_user_id`, `owner_name`, `class`, `trigger` (`ai_handoff` | `teammate`), `has_draft`, `draft_auto_send_at`, `draft_auto_send_at_label`, `waiting_since`, `waited_hours`, `waited_label`, `promise_overdue`, `promise_label`, `next` (`kind` `draft` | `step` | `held` | `none`, `at_label`, `label`: the next step as one short fact, from the same facts as `sentence`), `sentence`; a broken promise sorts first), `truncated`; null when the read failed). `today.not_sent` counts people with a step that ended without a send today and no send at all today. All copy is in `lang` (default the workspace’s). `after_handoff_now=false` leaves `after_handoff.now` null (read it from `/followups/recontacto/plan/after-handoff`). That screen read (`after_handoff_now=false` with no `scenario`, `start`, `contact_id` or `lead_id`) is served from a per-workspace copy at most 60 s old (refreshed in the background after 10 s), dropped by the same writes as the «Ahora» block, and carries `as_of` (when the read behind it started) and `age_seconds`; every other read is fresh.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "conversed",
                "never_replied",
                "lost"
              ]
            },
            "required": false,
            "name": "scenario",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "start",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "after_handoff_now",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The simulated plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "header_sentence": "4 mensajes en 5 días; a los 12 días se marca perdido y a los 42 días, uno más con tu aprobación.",
                    "start": "2026-10-21T16:00:00-03:00",
                    "start_options": [
                      {
                        "start": "2026-10-21T16:00:00-03:00",
                        "label": "mié 21 oct · 16:00"
                      },
                      {
                        "start": "2026-10-21T20:00:00-03:00",
                        "label": "mié 21 oct · 20:00"
                      }
                    ],
                    "timezone": "America/Santiago",
                    "contact_hours": {
                      "start_hour": 9,
                      "end_hour": 21
                    },
                    "enabled": true,
                    "scenarios": [
                      {
                        "scenario": "conversed",
                        "title": "Conversó y dejó de responder",
                        "enabled": true,
                        "sentence": "Le escribimos 3 horas y 8 horas después de nuestro último mensaje, por el mismo chat (si cae fuera del horario, a las 09:00 siguientes). Después, por WhatsApp, a los 2 y a los 5 días. Si sigue sin responder, a los 12 días lo marcamos perdido, y a los 42 días le escribimos una última vez, con tu aprobación.",
                        "start_label": "Si nuestro último mensaje sale",
                        "items": [
                          {
                            "kind": "message",
                            "n": 2,
                            "rail": "unanswered_message",
                            "step": 2,
                            "elapsed_hours": 8,
                            "elapsed_label": "8 horas",
                            "at": "2026-10-22T09:00:00-03:00",
                            "at_label": "jue 22 oct · 09:00",
                            "deferred_from": "2026-10-22T00:00:00-03:00",
                            "deferred_from_label": "jue 00:00",
                            "deferred_reason": "fuera de horario",
                            "deferred_code": "outside_contact_hours",
                            "deferred_detail": "Fuera de horario: sale a las 09:00. Las horas de noche no cuentan como atraso.",
                            "sends": true,
                            "not_sent_reason": null,
                            "channel": "same_chat",
                            "what": "Recordatorio en el mismo chat",
                            "detail": "La IA retoma lo que quedó pendiente; si no queda nada pendiente, no sale. Solo en chats abiertos de WhatsApp o Instagram.",
                            "autonomy_effective": "auto",
                            "autonomy_label": "Automático",
                            "autonomy_reason": null,
                            "needs_template": false,
                            "warnings": [],
                            "hot_at": null,
                            "hot_at_label": null,
                            "gap_label": "5 horas después del anterior"
                          },
                          {
                            "kind": "window_marker",
                            "elapsed_label": "24 horas",
                            "text": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                          },
                          {
                            "kind": "lost",
                            "elapsed_hours": 288,
                            "elapsed_label": "12 días",
                            "at": "2026-11-02T16:00:00-03:00",
                            "at_label": "lun 2 nov · 16:00",
                            "title": "Lo marcamos perdido",
                            "detail": "Si sigue sin responder 7 días después del último mensaje.",
                            "lose_after_days": 7
                          },
                          {
                            "kind": "end",
                            "elapsed_hours": null,
                            "elapsed_label": null,
                            "at": null,
                            "at_label": null,
                            "title": "Fin de este camino.",
                            "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                            "lose_after_days": null
                          }
                        ],
                        "footnote": "Los mensajes por día salen en la revisión de cada hora (hasta 1 hora después); los del chat, hasta 15 minutos después."
                      }
                    ],
                    "pacing_summary": "Ritmo · hasta 3 caminos sin respuesta por persona cada 90 días; no empezamos con silencios de más de 45 días; después de 24 horas, plantilla aprobada.",
                    "pacing": [
                      {
                        "rule": "R12",
                        "on": true,
                        "value": 3,
                        "text": "hasta 3 caminos sin respuesta por persona cada 90 días"
                      },
                      {
                        "rule": "R10",
                        "on": true,
                        "value": 45,
                        "text": "no empezamos con silencios de más de 45 días"
                      },
                      {
                        "rule": "R7",
                        "on": true,
                        "value": null,
                        "text": "después de 24 horas, plantilla aprobada"
                      },
                      {
                        "rule": "R1",
                        "on": false,
                        "value": null,
                        "text": "si no responde a un seguimiento, los otros caminos esperan N días (apagado)"
                      }
                    ],
                    "limits_sentence": "Nunca son infinitos: nunca más de 12 mensajes sin respuesta a una misma persona cada 90 días, sumando todas sus oportunidades, cada camino termina (dejó de responder: 4 mensajes, perdido a los 12 días; nunca respondió: 4 mensajes, perdido a los 12 días; ya perdido: 1 más y fin), como máximo 3 caminos sin respuesta por persona cada 90 días, 4 recordatorios en el chat por conversación sin respuesta, hasta 200 mensajes por día que tus clientes no pidieron y nunca dos mensajes automáticos a la misma persona con menos de 1 hora.",
                    "guards_summary": "Siempre se respeta · horario de 09:00 a 21:00, no contactar, ventana de WhatsApp y 15 reglas más",
                    "guards": [
                      {
                        "guard": "G21",
                        "text": "Revisamos hasta **2000** oportunidades por vuelta, primero las de silencio más reciente.",
                        "link": null,
                        "shown_as": [
                          "S"
                        ],
                        "emphasis": "highlight",
                        "note": "**Hoy a las 10:07** una revisión llegó al tope: revisamos **2.000** de **2.340** clientes; el resto entra en las próximas revisiones."
                      }
                    ],
                    "approval_fallback": {
                      "available": true,
                      "reason": null,
                      "hours": null
                    },
                    "after_handoff": {
                      "followups": "draft_for_owner",
                      "auto_send_after_hours": null,
                      "while_silent": "draft_and_remind",
                      "ai_answers_waiting": "never",
                      "ai_answers_waiting_after_minutes": 30,
                      "ai_answers_waiting_after_hours": null,
                      "ai_answers_waiting_keep_owner": false,
                      "reminder_minutes": 30,
                      "controls": {
                        "followups": true,
                        "auto_send_after_hours": true,
                        "ai_answers_waiting_after_hours": true,
                        "while_silent": true,
                        "ai_answers_waiting": true
                      },
                      "summary": "Después de pasarlo a una persona · borrador para quien lo atiende, sale solo si lo envía; si el cliente espera, le avisamos a quien lo atiende. La IA no le escribe a una conversación que atiende una persona hasta que alguien la devuelve a la IA.",
                      "now": {
                        "conversations": 2,
                        "by_class": {
                          "informational": 1,
                          "operational": 1,
                          "sensitive": 0,
                          "takeover": 0
                        },
                        "drafts": 1,
                        "upcoming_drafts": 0,
                        "drafts_self_sending": 1,
                        "customers_waiting": 0,
                        "sentence": "Ahora: 2 conversaciones con el equipo · 1 borrador por enviarse solo",
                        "people": [
                          {
                            "contact_id": "3f1c2b4a-5d6e-4f70-8a91-b2c3d4e5f607",
                            "contact_name": "Nando",
                            "conversation_id": "01a1639b-75bf-79d7-8e5a-93c5000d6319",
                            "conversation_display_id": "C-363",
                            "owner_user_id": "829e61ea-2cb9-43dc-80fd-18baf78eaaed",
                            "owner_name": "Diego Navarrete",
                            "class": "informational",
                            "trigger": "ai_handoff",
                            "has_draft": true,
                            "draft_auto_send_at": "2026-10-25T16:30:00-03:00",
                            "draft_auto_send_at_label": "mañana 16:30",
                            "waiting_since": null,
                            "waited_hours": null,
                            "waited_label": null,
                            "promise_overdue": false,
                            "promise_label": null,
                            "next": {
                              "kind": "draft",
                              "at_label": "mañana 16:30",
                              "label": "borrador · mañana 16:30"
                            },
                            "sentence": "Lo atiende Diego Navarrete · borrador listo · se envía solo mañana 16:30 si nadie lo revisa"
                          },
                          {
                            "contact_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
                            "contact_name": "Karen Olguin",
                            "conversation_id": "01a16d2e-9f3a-7b1c-8d2e-3f4a5b6c7d8e",
                            "conversation_display_id": "C-384",
                            "owner_user_id": "829e61ea-2cb9-43dc-80fd-18baf78eaaed",
                            "owner_name": "Diego Navarrete",
                            "class": "operational",
                            "trigger": "ai_handoff",
                            "has_draft": false,
                            "draft_auto_send_at": null,
                            "draft_auto_send_at_label": null,
                            "waiting_since": null,
                            "waited_hours": null,
                            "waited_label": null,
                            "promise_overdue": true,
                            "promise_label": "el vie 23 desde las 10:00 (no se cumplió)",
                            "next": {
                              "kind": "none",
                              "at_label": null,
                              "label": "le toca a Diego"
                            },
                            "sentence": "Lo atiende Diego · algo que solo puede hacer el equipo: la IA no escribe sola · la IA prometió que le escribirían el vie 23 desde las 10:00 (no se cumplió)"
                          }
                        ],
                        "truncated": false
                      }
                    },
                    "today": {
                      "since": "2026-10-21T03:00:00.000Z",
                      "sent": 66,
                      "not_sent": 31,
                      "went_to_approval": 0,
                      "awaiting_approval": 0,
                      "would_need_approval": 9,
                      "due": 31,
                      "failed": 2,
                      "failed_actionable": 1,
                      "backlog": {
                        "queued": 1,
                        "per_day": 10,
                        "days": 1,
                        "last_release_on": "2026-10-22",
                        "sentence": "En cola: 1, sale todo el jue 22 oct"
                      }
                    },
                    "queue": {
                      "per_day": 10,
                      "released_today": 4,
                      "waiting": 1,
                      "steps": [
                        {
                          "attempt_id": "7a7a7a7a-0000-4000-8000-0000000000a0",
                          "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                          "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                          "situation": "stalled",
                          "step": 2,
                          "due_at": "2026-10-12T19:00:00.000Z",
                          "position": 0,
                          "release_on": "2026-10-29",
                          "release_at": "2026-10-29T09:00:00-03:00",
                          "label": "En cola: sale el jue 29 oct"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/plan/after-handoff": {
      "get": {
        "summary": "The «Ahora» block of the plan, on its own",
        "description": "Exactly the plan’s `after_handoff.now`: the open conversations with the team now (`conversations`, `drafts_self_sending`, `customers_waiting`, `by_class`, `sentence`, `people`, `truncated`), or null when the read failed. It costs one next-step read per handed-off person, so a screen asks `/followups/recontacto/plan?after_handoff_now=false` and reads this block separately. `lang` picks es or en (default the workspace’s). Served from a per-workspace copy at most 60 s old (refreshed in the background after 10 s); a successful write on /conversations, /messages, /leads, /outbound/approvals, /outbound-holds, /followups or /tenant/settings drops it. `as_of` is when the read behind the answer started and `age_seconds` its age («actualizado hace N s»). The plan’s own `after_handoff.now` is always read fresh.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The «Ahora» block",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversations": 2,
                    "by_class": {
                      "informational": 1,
                      "operational": 1,
                      "sensitive": 0,
                      "takeover": 0
                    },
                    "drafts": 1,
                    "upcoming_drafts": 0,
                    "drafts_self_sending": 1,
                    "customers_waiting": 0,
                    "sentence": "Ahora: 2 conversaciones con el equipo · 1 borrador por enviarse solo",
                    "people": [
                      {
                        "contact_id": "3f1c2b4a-5d6e-4f70-8a91-b2c3d4e5f607",
                        "contact_name": "Nando",
                        "conversation_id": "01a1639b-75bf-79d7-8e5a-93c5000d6319",
                        "conversation_display_id": "C-363",
                        "owner_user_id": "829e61ea-2cb9-43dc-80fd-18baf78eaaed",
                        "owner_name": "Diego Navarrete",
                        "class": "informational",
                        "trigger": "ai_handoff",
                        "has_draft": true,
                        "draft_auto_send_at": "2026-10-25T16:30:00-03:00",
                        "draft_auto_send_at_label": "mañana 16:30",
                        "waiting_since": null,
                        "waited_hours": null,
                        "waited_label": null,
                        "promise_overdue": false,
                        "promise_label": null,
                        "next": {
                          "kind": "draft",
                          "at_label": "mañana 16:30",
                          "label": "borrador · mañana 16:30"
                        },
                        "sentence": "Lo atiende Diego Navarrete · borrador listo · se envía solo mañana 16:30 si nadie lo revisa"
                      },
                      {
                        "contact_id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0d",
                        "contact_name": "Karen Olguin",
                        "conversation_id": "01a16d2e-9f3a-7b1c-8d2e-3f4a5b6c7d8e",
                        "conversation_display_id": "C-384",
                        "owner_user_id": "829e61ea-2cb9-43dc-80fd-18baf78eaaed",
                        "owner_name": "Diego Navarrete",
                        "class": "operational",
                        "trigger": "ai_handoff",
                        "has_draft": false,
                        "draft_auto_send_at": null,
                        "draft_auto_send_at_label": null,
                        "waiting_since": null,
                        "waited_hours": null,
                        "waited_label": null,
                        "promise_overdue": true,
                        "promise_label": "el vie 23 desde las 10:00 (no se cumplió)",
                        "next": {
                          "kind": "none",
                          "at_label": null,
                          "label": "le toca a Diego"
                        },
                        "sentence": "Lo atiende Diego · algo que solo puede hacer el equipo: la IA no escribe sola · la IA prometió que le escribirían el vie 23 desde las 10:00 (no se cumplió)"
                      }
                    ],
                    "truncated": false,
                    "as_of": "2026-10-21T19:00:00.000Z",
                    "age_seconds": 12
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/next-steps": {
      "get": {
        "summary": "What happens next for each of a contact’s leads",
        "description": "The contact page’s «Próximo seguimiento»: for each open or lost lead of `contact_id` (or the one `lead_id`), newest first, the next thing the Recontacto engine does, computed by the same ladder walk and decision the sweep runs (contact hours, the night deferral, R12, the answer-debt gate). `state` is `message` (a follow-up is planned: `step` of `steps_total`, `at`, `channel`, `what`, `autonomy`, `deferred_from_label` when contact hours moved it, `due_now` when the next sweep takes it; with today’s daily cap (G2) or review budget (G41) spent, a step due today moves to tomorrow’s opening and `deferred_reason` says which). `at` is never outside contact hours: a step due at night (or a G8 re-look that opens at midnight) moves to the next opening. `deferred_codes` lists every reason `at` moved (`outside_contact_hours`, `re_evaluation`, `daily_cap_reached`, `review_budget_reached`), `lose` (no more messages; marked lost at `at`), `answer_debt` (the customer wrote last), `path_cap` (R12), `person_ladder` (G13: the person’s ladder runs on a newer lead), `undeliverable` (G35: WhatsApp refused our messages; nothing goes out until they write), `held` (the sweep holds the step when it comes due, asked at that moment through the sweep’s own functions: G25 `no_reachable_address` (no phone or email for the step’s channel), G52 a visit fixed, the store visited or a sale under way; G50 handed to a person for a complaint or a step only a person can do; G49 «Nada»; G34, G36, R13 / G38, R5, G43; and the composer’s channel resolution: `no_deliverable_channel` (e.g. Instagram past its window), `template_missing`, `policy_blocked`, `account_restricted`. `held` carries the guard, `code` and `until` when known; never a send time), `draft` (a draft of the step is written and waits on a person: `draft` = `attempt_id`, `auto_send_at` and `auto_send_at_label` when G49 sends it itself, null when only a person sends it), `queued` (G24 backlog queue: `queue` = `position` (1-based), `waiting`, `per_day`, `label`; `at` is the estimated release), `paused` (the master switch is off) or `none`. `sentence` is the whole answer in one line, in `lang` (default the workspace’s). `held` is today’s latest hold of a due step (`guard`, `code`, `since`, `last_seen`, `until`, `sweeps`), or null. `step_label` names the step as the hero does («Conversó y dejó de responder · paso 3 de 5»), and `sentence` uses it. `at` is the lead sweep’s hourly tick (the grid `/plan` draws). `handoff_draft` is set when the step becomes a draft for the person handling a handed-off conversation (G49: `approver_user_id`, `auto_send_at`, `auto_send_at_label`, `window_note`; `auto_send_at` is capped inside the channel’s 24 h window exactly as the engine stores it, null when only a person can send it), else null. When the step resolves to another channel than configured (an Instagram step past its window goes as a WhatsApp template), `channel` and `what` name the resolved one. `conversations` is the chat rail’s next reminder on the person’s open WhatsApp / Instagram threads, from the chat sweep’s own candidate query and hold spacing: the same fields as a lead plus `conversation_id`, `thread_channel` and `situation: unanswered_message`. `handoff` is the person’s open conversation the AI handed to a person, or null: `conversation_id`, `owner_user_id`, `owner_name`, `mode` (`after_handoff.followups`), `class` (`informational` | `operational` | `sensitive`: why it was handed over; since 2026-10-25 it no longer decides anything: every handed-off or taken conversation is silent until «Devolver a la IA», its steps follow `mode`, and the AI answers a waiting customer only when `after_handoff.ai_answers_waiting` allows it), `draft` (the pending draft: `attempt_id`, `auto_send_at`, `auto_send_at_label`, `promise` when a teammate’s promised time set it, `window_note`: when it must be sent by on a free-text WhatsApp or Instagram draft), `waiting` (the customer wrote last: `since`, `ai_answers_at`, `ai_answers_at_label`), `next` (`kind` `draft` | `step` | `held` | `none`, `at_label`, `label`: the next step as one short fact) and `sentence`, the state line («Lo atiende Diego · borrador listo · se envía solo mañana 16:30 si nadie lo revisa»). With no draft and nobody waiting, the line names the person’s actual next step: «borrador para quien lo atiende · {fecha}», the hold’s own words, or «sin seguimiento automático: le toca a {owner}».",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Next step per lead",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "timezone": "America/Santiago",
                    "leads": [
                      {
                        "lead_id": "d0125f39-6f0a-4c1e-9b7a-2a4f1c3e8b10",
                        "state": "message",
                        "situation": "never_replied",
                        "step": 2,
                        "steps_total": 4,
                        "at": "2026-10-23T20:00:00-03:00",
                        "at_label": "vie 23 oct · 20:00",
                        "elapsed_label": "8 horas",
                        "due_now": false,
                        "deferred_from_label": null,
                        "deferred_reason": null,
                        "channel": "whatsapp",
                        "what": "WhatsApp",
                        "autonomy": "auto",
                        "autonomy_label": "Automático",
                        "sentence": "Próximo seguimiento: paso 2 de 4 · WhatsApp · vie 23 oct · 20:00",
                        "handoff_draft": null
                      },
                      {
                        "lead_id": "5b0e7c21-9d3a-4f6e-8c1b-3a2d4e5f6a7b",
                        "state": "held",
                        "situation": "stalled",
                        "step": 1,
                        "steps_total": 2,
                        "at": null,
                        "at_label": null,
                        "elapsed_label": null,
                        "due_now": false,
                        "deferred_from_label": null,
                        "deferred_reason": null,
                        "channel": "whatsapp",
                        "what": null,
                        "autonomy": null,
                        "autonomy_label": null,
                        "sentence": "En pausa: visita agendada, ya visitó o venta en curso (se retoma el jue 5 nov)",
                        "held": {
                          "guard": "G52",
                          "code": "bottom_funnel",
                          "label": "visita agendada, ya visitó o venta en curso",
                          "since": "2026-10-23T15:10:00-03:00",
                          "last_seen": "2026-10-23T15:10:00-03:00",
                          "until": "2026-11-05T13:00:00-03:00",
                          "sweeps": 0
                        },
                        "handoff_draft": null
                      }
                    ],
                    "handoff": {
                      "conversation_id": "01a1639b-75bf-79d7-8e5a-93c5000d6319",
                      "owner_user_id": "829e61ea-2cb9-43dc-80fd-18baf78eaaed",
                      "owner_name": "Diego Navarrete",
                      "mode": "draft_for_owner",
                      "class": "informational",
                      "sensitive": false,
                      "draft": {
                        "attempt_id": "7c2f1e0a-3b4d-4e5f-8a9b-0c1d2e3f4a5b",
                        "auto_send_at": "2026-10-25T16:30:00-03:00",
                        "auto_send_at_label": "mañana 16:30",
                        "promise": false
                      },
                      "waiting": null,
                      "next": {
                        "kind": "draft",
                        "at_label": "mañana 16:30",
                        "label": "borrador · mañana 16:30"
                      },
                      "sentence": "Lo atiende Diego · borrador listo · se envía solo mañana 16:30 si nadie lo revisa"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/pause": {
      "get": {
        "summary": "Safe pause: live pauses and who changed them",
        "description": "Escribir primero. `pauses` holds the live pause of each scope (`all`, `builtin:al-llegar`, `builtin:si-no-responde`, `builtin:avisos`, or a custom sequence id); `history` lists the latest changes, newest first, with who paused (`pausedBy`) and who or what resumed (`resumedBy`, `resumeReason` = `manual` or `until`). The workspace switch (`recontacto.enabled`) keeps working on its own.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "history_limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Live pauses and history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pauses": [
                      {
                        "id": "0198f0c0-0000-7000-8000-000000000001",
                        "scope": "builtin:si-no-responde",
                        "queuePolicy": "keep",
                        "pausedAt": "2026-11-02T14:00:00.000Z",
                        "pausedBy": {
                          "kind": "supabase_user",
                          "id": "7a7a7a7a-0000-4000-8000-000000000060",
                          "via": null
                        },
                        "until": "2026-11-09T12:00:00.000Z",
                        "resumedAt": null,
                        "resumedBy": null,
                        "resumeReason": null,
                        "discardedCount": 0
                      }
                    ],
                    "history": [
                      {
                        "id": "0198f0c0-0000-7000-8000-000000000001",
                        "scope": "builtin:si-no-responde",
                        "queuePolicy": "keep",
                        "pausedAt": "2026-11-02T14:00:00.000Z",
                        "pausedBy": {
                          "kind": "supabase_user",
                          "id": "7a7a7a7a-0000-4000-8000-000000000060",
                          "via": null
                        },
                        "until": "2026-11-09T12:00:00.000Z",
                        "resumedAt": null,
                        "resumedBy": null,
                        "resumeReason": null,
                        "discardedCount": 0
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Pause what Vitrina writes first (all, or one sequence)",
        "description": "Pausing again with the same `scope` replaces `until` and `queuePolicy`. `until` resumes by itself (checked every minute). `queuePolicy`: `keep` leaves queued messages queued, and they go out on resume through the usual contact hours (from 9:00); `discard` cancels them with reason `paused_discarded` (never deleted) and cancels whatever falls due meanwhile; a standing Búsqueda stays open. A pause never stops the AI from answering conversations a customer opened, and never blocks a message a person approves by hand.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`all`, a built-in sequence id (`builtin:al-llegar`, `builtin:si-no-responde`, `builtin:avisos`) or a custom sequence id."
                  },
                  "until": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Resume by itself at this instant (ISO 8601, in the future)."
                  },
                  "queuePolicy": {
                    "type": "string",
                    "enum": [
                      "keep",
                      "discard"
                    ],
                    "description": "`keep`: queued messages go out when it resumes. `discard`: they are cancelled with a reason."
                  }
                },
                "required": [
                  "scope",
                  "queuePolicy"
                ]
              },
              "example": {
                "scope": "builtin:si-no-responde",
                "until": "2026-11-09T12:00:00.000Z",
                "queuePolicy": "keep"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The live pause",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0198f0c0-0000-7000-8000-000000000001",
                    "scope": "builtin:si-no-responde",
                    "queuePolicy": "keep",
                    "pausedAt": "2026-11-02T14:00:00.000Z",
                    "pausedBy": {
                      "kind": "supabase_user",
                      "id": "7a7a7a7a-0000-4000-8000-000000000060",
                      "via": null
                    },
                    "until": "2026-11-09T12:00:00.000Z",
                    "resumedAt": null,
                    "resumedBy": null,
                    "resumeReason": null,
                    "discardedCount": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/resume": {
      "post": {
        "summary": "Resume a paused scope",
        "description": "Idempotent: `resumed` is false when the scope was not paused. Messages kept by the pause go out as their own schedules and the contact hours allow.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80,
                    "description": "`all`, a built-in sequence id (`builtin:al-llegar`, `builtin:si-no-responde`, `builtin:avisos`) or a custom sequence id."
                  }
                },
                "required": [
                  "scope"
                ]
              },
              "example": {
                "scope": "builtin:si-no-responde"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "scope": "builtin:si-no-responde",
                    "resumed": true,
                    "pause": {
                      "id": "0198f0c0-0000-7000-8000-000000000001",
                      "scope": "builtin:si-no-responde",
                      "queuePolicy": "keep",
                      "pausedAt": "2026-11-02T14:00:00.000Z",
                      "pausedBy": {
                        "kind": "supabase_user",
                        "id": "7a7a7a7a-0000-4000-8000-000000000060",
                        "via": null
                      },
                      "until": "2026-11-09T12:00:00.000Z",
                      "resumedAt": "2026-11-03T09:00:00.000Z",
                      "resumedBy": null,
                      "resumeReason": "manual",
                      "discardedCount": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/write-first": {
      "get": {
        "summary": "What Vitrina writes first, one sentence per moment",
        "description": "The «Escribir primero» header: one sentence per proactive tab, generated from the workspace’s real settings, never fixed copy. `tabs` always holds, in this order, `first_touch` («Al llegar»: which lead sources get a first message, on which channel, after the first-touch delay), `recontacto` («Si no responde»: exactly `GET /followups/recontacto/plan` `header_sentence`), `followups` («Avisos»: the active follow-up recipes, grouped by trigger) and `post_atencion` («Después de atenderlo»: armed service protocols and, for clinics, the presupuesto follow-up). `on` is false when that rail sends nothing today. `pointers` lists the rails that also write first but are set elsewhere, only when they do: `appointment_reminders`, `automation_rules` (active rules that send a message), `surveys` (the review request), `waitlist` (clinics only) and `campaigns` (always). Each item carries the screen it links to (`href`). Copy is in `lang` (default the workspace’s).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The header sentences and pointers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "es",
                    "tabs": [
                      {
                        "tab": "first_touch",
                        "label": "Al llegar",
                        "sentence": "Un lead del sitio web recibe un WhatsApp a los 5 min si no nos escribió antes.",
                        "on": true,
                        "href": "/settings/first-touch"
                      },
                      {
                        "tab": "recontacto",
                        "label": "Si no responde",
                        "sentence": "4 mensajes en 5 días; a los 12 días se marca perdido y a los 42 días, uno más con tu aprobación.",
                        "on": true,
                        "href": "/settings/seguimientos?tab=recontacto"
                      },
                      {
                        "tab": "followups",
                        "label": "Avisos",
                        "sentence": "1 aviso activo: lo que el cliente pidió que le recordáramos.",
                        "on": true,
                        "href": "/settings/seguimientos?tab=followups"
                      },
                      {
                        "tab": "post_atencion",
                        "label": "Después de atenderlo",
                        "sentence": "Sin pautas activas.",
                        "on": false,
                        "href": "/settings/seguimientos?tab=post-atencion"
                      }
                    ],
                    "pointers": [
                      {
                        "pointer": "appointment_reminders",
                        "label": "Recordatorios de cita",
                        "sentence": "Un recordatorio 2 horas antes de cada cita.",
                        "href": "/settings/recordatorios"
                      },
                      {
                        "pointer": "campaigns",
                        "label": "Campañas",
                        "sentence": "Solo salen cuando lanzas una campaña; ninguna en curso.",
                        "href": "/campaigns"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/attention": {
      "get": {
        "summary": "What needs my attention in «Escribir primero»",
        "description": "One read for the «Escribir primero» attention strip. `pending.count` is the single «esperan tu OK» number: proactive messages waiting for approval, merged across every rail that queues one (`by_source` splits it by rail). It is computed with the same predicate as `GET /outbound/approvals`, so it always equals that list’s `total`. `ai_review_limit` is today’s AI reviews (`used` of `total`) and `resumes_at` (ISO) once it is spent, else null. `channel_problems` lists disconnected channels that stop sends with the Avisos waiting on each. `items` are other things needing a person (templates). `quiet` holds the calm numbers: `sent_today`, `in_queue` (count and when the next one may go) and `not_sent` (count plus `reasons`). Every entry that leads somewhere carries `action_target`, a typed destination (`record` | `list` | `page`) the UI routes. Copy for `reasons` is in `lang` (default the workspace’s).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The attention summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "pending": {
                      "count": 5,
                      "by_source": [
                        {
                          "source": "recontacto",
                          "count": 4
                        },
                        {
                          "source": "service_lifecycle",
                          "count": 1
                        }
                      ],
                      "action_target": {
                        "kind": "list",
                        "screen": "conversations",
                        "filter": {
                          "view": "inbox-approvals"
                        }
                      }
                    },
                    "ai_review_limit": {
                      "used": 200,
                      "total": 200,
                      "resumes_at": "2026-11-03T09:00:00-03:00"
                    },
                    "channel_problems": [
                      {
                        "kind": "email_not_connected",
                        "channel": "email",
                        "count": 3,
                        "action_target": {
                          "kind": "page",
                          "page": "settings.channels"
                        }
                      }
                    ],
                    "items": [
                      {
                        "kind": "waiting_template",
                        "count": 2,
                        "action_target": {
                          "kind": "page",
                          "page": "settings.templates"
                        }
                      }
                    ],
                    "quiet": {
                      "sent_today": 12,
                      "in_queue": {
                        "count": 7,
                        "until": "2026-11-03T09:00:00-03:00"
                      },
                      "not_sent": {
                        "count": 9,
                        "reasons": [
                          {
                            "reason": "Habló con un compañero hace poco",
                            "count": 9
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/readiness": {
      "get": {
        "summary": "Can Recontacto actually send?",
        "description": "WhatsApp connection, how many APPROVED templates Vitrina can fill, and the 30-day counts of sends stuck for want of a template versus sends the model deliberately abstained from. `auto_template` describes the template Vitrina drafted and submitted on the tenant’s behalf (`pending` → in Meta review, `approved`, `gave_up` → three rejections, a human must author one) and how many rungs are parked waiting on it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Readiness",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "whatsapp_connected": true,
                    "usable_templates": 2,
                    "template_names": [
                      "reactivar_cotizacion",
                      "seguimiento_visita"
                    ],
                    "wrong_category_templates": 0,
                    "skips_missing_template_30d": 0,
                    "abstentions_30d": 4,
                    "auto_template": {
                      "status": "pending",
                      "template_id": "7a7a7a7a-0000-4000-8000-0000000000c0",
                      "template_name": "reactivar_conversacion",
                      "language": "es",
                      "submitted_at": "2026-10-09T12:00:00.000Z",
                      "rejection_reason": null,
                      "rejected_count": 0,
                      "waiting_attempts": 2
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/run": {
      "post": {
        "summary": "Run the Recontacto sweep now",
        "description": "Forces both sweeps regardless of schedule, enablement or quiet hours. Only a caller who also holds `campaigns:write` triggers the auto-send legs; everyone else gets a review-queue-only sweep. `waiting_template` counts rungs parked on a template Vitrina just asked Meta for — neither sent nor skipped, and no budget spent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Sweep result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "evaluated": 12,
                    "truncated": false,
                    "attempted": 5,
                    "sent": 0,
                    "queued": 5,
                    "skipped": 7,
                    "lost": 0,
                    "capped": 0,
                    "waiting_template": 0,
                    "answer_debt": 0,
                    "contact_deduped": 0,
                    "unreachable": 0,
                    "transient": 0,
                    "expired": 0,
                    "halted": false,
                    "unanswered_message": {
                      "evaluated": 0,
                      "sent": 0,
                      "queued": 0,
                      "skipped": 0,
                      "transient": 0,
                      "halted": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/recontacto/attempts": {
      "get": {
        "summary": "List Recontacto attempts (ledger and review queue)",
        "description": "One row per cadence rung (with `no_send_code`, the «no enviados» drill, each row also carries `step_label`, the step as the hero names it). `exhausted` means the rung spent its evaluation budget without ever reaching the customer — never read it as \"we chased them\". `waiting_template` means it is parked on a WhatsApp template in Meta review: no budget spent, the ladder did not move, and the template hooks own the row. `queued` is an overdue step held by the backlog rule (G24): it waits for its release slot (at most 10 per workspace per day), is never composed and never counts as a try until released; `release_estimate_at` says when it is expected to go out. A `failed` row carries `failure`: `reason`, the owner’s sentence in `lang`, and `next` (`retry`, `wait_for_reply` when WhatsApp cannot deliver to the person and we wait until they write, `retry_on` when Meta holds marketing back from the number for an experiment (130472) and we try again by ourselves on `retry_on`, `fix_template`, or `none`; `retry_on` is null otherwise). `statuses` filters several statuses at once; `limit` and `cursor` page the history (`meta.pagination.nextCursor`), and `meta.pagination.total` counts every row the filter matches.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "proposed",
                "dismissed",
                "sent",
                "failed",
                "skipped",
                "exhausted",
                "waiting_template",
                "queued"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "never_replied",
                "stalled",
                "lost",
                "unanswered_message"
              ]
            },
            "required": false,
            "name": "situation",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9_]+(,[a-z0-9_]+)*$"
            },
            "required": false,
            "name": "no_send_code",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "not_sent_since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+(,[a-z_]+)*$"
            },
            "required": false,
            "name": "statuses",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Recontacto attempts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-0000000000a0",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                      "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                      "conversation_id": null,
                      "assignee_user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                      "situation": "stalled",
                      "step": 1,
                      "channel": "whatsapp",
                      "status": "proposed",
                      "reason": "Pidió cotización hace 6 días y no respondió; el auto que le interesaba sigue disponible.",
                      "draft_message": "Hola María, ¿pudiste revisar la cotización? El auto sigue disponible y puedo agendarte una visita.",
                      "email_subject": null,
                      "template_id": null,
                      "template_params": null,
                      "outbound_action_id": "7a7a7a7a-0000-4000-8000-0000000000b0",
                      "read_message_id": null,
                      "score_at_attempt": 72,
                      "contact_attempts": 0,
                      "evaluation_attempts": 1,
                      "last_evaluated_at": "2026-10-10T13:00:00.000Z",
                      "no_send_code": null,
                      "no_send_reason": null,
                      "error": null,
                      "sent_at": null,
                      "dismissed_at": null,
                      "outcome": null,
                      "outcome_at": null,
                      "outcome_value_amount": null,
                      "created_at": "2026-10-10T13:00:00.000Z",
                      "lead_title": "Mazda CX-5 2021",
                      "contact_name": "María González",
                      "contact_phone": "+56 9 1234 5678",
                      "contact_email": "maria.gonzalez@example.cl",
                      "lead_conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                      "template_name": null,
                      "last_inbound_at": "2026-10-04T16:20:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "nextCursor": null,
                      "limit": 200
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/stats": {
      "get": {
        "summary": "Recontacto results by situation, rung and channel",
        "description": "One row per (situation, rung, channel) over the last `since_days` (default 30, max 365), counting only rungs that were actually sent: `sent`, `answered` (live: sends the customer has written after, by now), `replied` (the judged outcome: any inbound after the send, including reactivated and won), `won` and `recovered_value`, the value of leads closed won after the nudge. Outcomes are judged a week after the send, so the latest rungs read as unreplied. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365
            },
            "required": false,
            "name": "since_days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Recontacto stats",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "situation": "stalled",
                      "step": 1,
                      "channel": "whatsapp",
                      "sent": 18,
                      "replied": 6,
                      "won": 1,
                      "recovered_value": 17500000
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/attempts/{id}/approve": {
      "post": {
        "summary": "Approve a Recontacto draft and send it",
        "description": "Approval and delivery are the same call. It refuses with 409 when the customer answered after the draft was composed, or when the proposal is older than its shelf life. `id` is an attempt.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The sent attempt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a0",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                    "conversation_id": null,
                    "assignee_user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "situation": "stalled",
                    "step": 1,
                    "channel": "whatsapp",
                    "status": "sent",
                    "reason": "Pidió cotización hace 6 días y no respondió; el auto que le interesaba sigue disponible.",
                    "draft_message": "Hola María, ¿pudiste revisar la cotización? El auto sigue disponible y puedo agendarte una visita.",
                    "email_subject": null,
                    "template_id": null,
                    "template_params": null,
                    "outbound_action_id": "7a7a7a7a-0000-4000-8000-0000000000b0",
                    "read_message_id": null,
                    "score_at_attempt": 72,
                    "contact_attempts": 1,
                    "evaluation_attempts": 1,
                    "last_evaluated_at": "2026-10-10T13:00:00.000Z",
                    "no_send_code": null,
                    "no_send_reason": null,
                    "error": null,
                    "sent_at": "2026-10-10T14:05:00.000Z",
                    "dismissed_at": null,
                    "outcome": null,
                    "outcome_at": null,
                    "outcome_value_amount": null,
                    "created_at": "2026-10-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/recontacto/attempts/{id}/dismiss": {
      "post": {
        "summary": "Dismiss a Recontacto draft",
        "description": "Discards a proposed draft without sending anything: the attempt moves to `dismissed` and the queued outbound action is cancelled. `id` is an attempt. Unlike approve it originates no message, so it needs only `followups:write`; a caller who cannot see the lead the attempt belongs to gets a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The dismissed attempt",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-0000000000a0",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                    "conversation_id": null,
                    "assignee_user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "situation": "stalled",
                    "step": 1,
                    "channel": "whatsapp",
                    "status": "dismissed",
                    "reason": "Pidió cotización hace 6 días y no respondió; el auto que le interesaba sigue disponible.",
                    "draft_message": "Hola María, ¿pudiste revisar la cotización? El auto sigue disponible y puedo agendarte una visita.",
                    "email_subject": null,
                    "template_id": null,
                    "template_params": null,
                    "outbound_action_id": "7a7a7a7a-0000-4000-8000-0000000000b0",
                    "read_message_id": null,
                    "score_at_attempt": 72,
                    "contact_attempts": 0,
                    "evaluation_attempts": 1,
                    "last_evaluated_at": "2026-10-10T13:00:00.000Z",
                    "no_send_code": "no_reason",
                    "no_send_reason": "descartado por una persona",
                    "error": null,
                    "sent_at": null,
                    "dismissed_at": "2026-10-10T14:05:00.000Z",
                    "outcome": null,
                    "outcome_at": null,
                    "outcome_value_amount": null,
                    "created_at": "2026-10-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/lead-review": {
      "get": {
        "summary": "Get lead review config and the score calibration curve",
        "description": "The reviewer reads a lead once and returns three verdicts: the score that gates Recontacto’s expensive rungs, the engagement temperature that paces and orders them, and a stage opinion. The calibration curve is the falsification: of the leads scored 80-90, how many actually closed won.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "responses": {
          "200": {
            "description": "Config and calibration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "config": {
                      "enabled": true,
                      "stage_autonomy": "auto",
                      "auto_confidence": 0.6,
                      "half_life_days": 30,
                      "instructions": ""
                    },
                    "calibration": [
                      {
                        "bucket": 3,
                        "n": 14,
                        "won_rate": 0.07
                      },
                      {
                        "bucket": 8,
                        "n": 11,
                        "won_rate": 0.64
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update lead review configuration",
        "description": "Partial patch; an empty body is a 400. Sets whether the reviewer runs (`enabled`), how far a stage verdict may go on its own (`stage_autonomy`: `auto`, `suggest` or `off`), the confidence floor for an automatic move (`auto_confidence`, 0..1), the days of silence after which a score halves (`half_life_days`) and the workspace’s own addendum (`instructions`, up to 4000 characters), and the Jev agreement doors (`agreement_doors`: stages, by slug, that Jev may move a lead forward into when a measured question such as `visit_to_buy` clears its threshold; replaces the whole list). Answers the merged config plus the calibration curve. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "stage_autonomy": {
                    "type": "string",
                    "enum": [
                      "auto",
                      "suggest",
                      "off"
                    ]
                  },
                  "auto_confidence": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1
                  },
                  "instructions": {
                    "type": "string",
                    "maxLength": 4000
                  },
                  "half_life_days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 365
                  },
                  "agreement_doors": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "stage_slug": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "question": {
                          "type": "string",
                          "enum": [
                            "visit_to_buy"
                          ]
                        }
                      },
                      "required": [
                        "stage_slug",
                        "question"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 20
                  }
                }
              },
              "example": {
                "stage_autonomy": "suggest",
                "half_life_days": 21
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated config",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "config": {
                      "enabled": true,
                      "stage_autonomy": "suggest",
                      "auto_confidence": 0.6,
                      "half_life_days": 21,
                      "instructions": ""
                    },
                    "calibration": [
                      {
                        "bucket": 3,
                        "n": 14,
                        "won_rate": 0.07
                      },
                      {
                        "bucket": 8,
                        "n": 11,
                        "won_rate": 0.64
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/subscriptions": {
      "get": {
        "summary": "List a contact’s follow-up subscriptions",
        "description": "`contact_id` is required — this is the chat panel’s per-contact view, not a tenant-wide list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contact_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Subscriptions for the contact",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000020",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                      "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                      "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                      "status": "open",
                      "criteria": {
                        "make": "Mazda",
                        "model": "CX-5",
                        "year_min": 2020,
                        "max_price": 18000000
                      },
                      "fire_at": null,
                      "fired_at": null,
                      "fired_channel": null,
                      "pending_event": null,
                      "pending_fire_at": null,
                      "pending_meta": null,
                      "pending_reason": null,
                      "preferred_channel": "whatsapp",
                      "created_by_kind": "human_user",
                      "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                      "fulfiller_kind": "ai_agent",
                      "fulfiller_user_id": null,
                      "consent_message_id": null,
                      "outbound_action_id": null,
                      "satisfied_by_message_id": null,
                      "failure_reason": null,
                      "terminal_reason": null,
                      "defer_count": 0,
                      "notice_count": 0,
                      "notice_failure_notified_cause": null,
                      "last_notice_at": null,
                      "last_notice_unkept_at": null,
                      "busqueda_swept_at": null,
                      "expiry_warned_at": null,
                      "expires_at": "2027-01-08T14:00:00.000Z",
                      "customer_deadline_at": null,
                      "fulfilled_at": null,
                      "fulfilled_by": null,
                      "fulfilled_vehicle_id": null,
                      "fulfilment_source": null,
                      "cancelled_at": null,
                      "cancelled_by": null,
                      "cancelled_by_kind": null,
                      "cancel_reason_code": null,
                      "cancel_reason": null,
                      "created_at": "2026-10-10T14:00:00.000Z",
                      "followup_name": "Avisar cuando llegue el auto que busca",
                      "trigger_key": "vehicle_in_stock"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Subscribe a contact to a follow-up",
        "description": "Upsert, not insert: re-subscribing a contact that already has an open subscription to the same recipe updates it rather than duplicating, and still answers 201. Two rejections worth knowing — an inactive recipe is a 409, and an `after_delay` recipe without `fire_at` is a 400, since there would be no moment to fire at. `fulfiller_kind` decides who performs it: the AI agent, or the operator themselves. `customer_deadline_at` records the deadline the CUSTOMER gave for a stock or price want — it expires on whichever comes first, that date or the platform’s 90 days.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "followup_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "conversation_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "criteria": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "fire_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "preferred_channel": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "whatsapp",
                      "email",
                      "sms",
                      "voice"
                    ]
                  },
                  "fulfiller_kind": {
                    "type": "string",
                    "enum": [
                      "ai_agent",
                      "human_self"
                    ]
                  },
                  "customer_deadline_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                },
                "required": [
                  "followup_id",
                  "contact_id"
                ]
              },
              "example": {
                "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                "criteria": {
                  "make": "Mazda",
                  "model": "CX-5",
                  "year_min": 2020,
                  "max_price": 18000000
                },
                "preferred_channel": "whatsapp",
                "fulfiller_kind": "ai_agent"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created or updated subscription",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "status": "open",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "fire_at": null,
                    "fired_at": null,
                    "fired_channel": null,
                    "pending_event": null,
                    "pending_fire_at": null,
                    "pending_meta": null,
                    "pending_reason": null,
                    "preferred_channel": "whatsapp",
                    "created_by_kind": "human_user",
                    "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfiller_kind": "ai_agent",
                    "fulfiller_user_id": null,
                    "consent_message_id": null,
                    "outbound_action_id": null,
                    "satisfied_by_message_id": null,
                    "failure_reason": null,
                    "terminal_reason": null,
                    "defer_count": 0,
                    "notice_count": 0,
                    "notice_failure_notified_cause": null,
                    "last_notice_at": null,
                    "last_notice_unkept_at": null,
                    "busqueda_swept_at": null,
                    "expiry_warned_at": null,
                    "expires_at": "2027-01-08T14:00:00.000Z",
                    "customer_deadline_at": null,
                    "fulfilled_at": null,
                    "fulfilled_by": null,
                    "fulfilled_vehicle_id": null,
                    "fulfilment_source": null,
                    "cancelled_at": null,
                    "cancelled_by": null,
                    "cancelled_by_kind": null,
                    "cancel_reason_code": null,
                    "cancel_reason": null,
                    "created_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/followups/subscriptions/{id}/fulfill": {
      "post": {
        "summary": "Mark a Búsqueda fulfilled (the customer got a car)",
        "description": "A stock-arrival or price-drop subscription IS the customer’s **Búsqueda** (ADR 0109): it stays OPEN across notices — one notice per matching car, at most one a day — and ends when the car turns up, the customer withdraws, or its clock runs out. This is the \"turned up\" end, and the unit that answered it is required: it is what keeps the demand list true and the sale attributable. It also happens by itself when a reservation or a sale of a car the want was told about is recorded for that contact. A timer Seguimiento is a 400 (it is owed once and done when it is sent), a want that is no longer open a 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "vehicle_id"
                ]
              },
              "example": {
                "vehicle_id": "7a7a7a7a-0000-4000-8000-000000000070"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The fulfilled Búsqueda",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "status": "fulfilled",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "fire_at": null,
                    "fired_at": null,
                    "fired_channel": null,
                    "pending_event": null,
                    "pending_fire_at": null,
                    "pending_meta": null,
                    "pending_reason": null,
                    "preferred_channel": "whatsapp",
                    "created_by_kind": "human_user",
                    "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfiller_kind": "ai_agent",
                    "fulfiller_user_id": null,
                    "consent_message_id": null,
                    "outbound_action_id": null,
                    "satisfied_by_message_id": null,
                    "failure_reason": null,
                    "terminal_reason": null,
                    "defer_count": 0,
                    "notice_count": 1,
                    "notice_failure_notified_cause": null,
                    "last_notice_at": "2026-10-12T15:00:00.000Z",
                    "last_notice_unkept_at": null,
                    "busqueda_swept_at": null,
                    "expiry_warned_at": null,
                    "expires_at": "2027-01-08T14:00:00.000Z",
                    "customer_deadline_at": null,
                    "fulfilled_at": "2026-10-14T16:00:00.000Z",
                    "fulfilled_by": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfilled_vehicle_id": "7a7a7a7a-0000-4000-8000-000000000070",
                    "fulfilment_source": "manual",
                    "cancelled_at": null,
                    "cancelled_by": null,
                    "cancelled_by_kind": null,
                    "cancel_reason_code": null,
                    "cancel_reason": null,
                    "created_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/subscriptions/{id}/cancel": {
      "post": {
        "summary": "Cancel a Búsqueda with a reason",
        "description": "The seller withdraws one open Búsqueda. Distinct from `DELETE /followups/subscriptions/{id}`, which merely unschedules and records no reason: \"bought elsewhere\" and \"could not be reached\" are different facts, and the demand list is read to decide what stock to buy. The customer’s own opt-out is not this endpoint — it cancels every open Búsqueda of that contact in the rail’s scope (ADR 0108 §5).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason_code": {
                    "type": "string",
                    "enum": [
                      "bought_elsewhere",
                      "no_longer_interested",
                      "unreachable",
                      "duplicate",
                      "other"
                    ]
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "required": [
                  "reason_code"
                ]
              },
              "example": {
                "reason_code": "bought_elsewhere",
                "reason": "Compró en otra automotora la semana pasada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled Búsqueda",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "status": "cancelled",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "fire_at": null,
                    "fired_at": null,
                    "fired_channel": null,
                    "pending_event": null,
                    "pending_fire_at": null,
                    "pending_meta": null,
                    "pending_reason": null,
                    "preferred_channel": "whatsapp",
                    "created_by_kind": "human_user",
                    "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfiller_kind": "ai_agent",
                    "fulfiller_user_id": null,
                    "consent_message_id": null,
                    "outbound_action_id": null,
                    "satisfied_by_message_id": null,
                    "failure_reason": null,
                    "terminal_reason": null,
                    "defer_count": 0,
                    "notice_count": 0,
                    "notice_failure_notified_cause": null,
                    "last_notice_at": null,
                    "last_notice_unkept_at": null,
                    "busqueda_swept_at": null,
                    "expiry_warned_at": null,
                    "expires_at": "2027-01-08T14:00:00.000Z",
                    "customer_deadline_at": null,
                    "fulfilled_at": null,
                    "fulfilled_by": null,
                    "fulfilled_vehicle_id": null,
                    "fulfilment_source": null,
                    "cancelled_at": "2026-10-14T16:00:00.000Z",
                    "cancelled_by": "7a7a7a7a-0000-4000-8000-000000000060",
                    "cancelled_by_kind": "user",
                    "cancel_reason_code": "bought_elsewhere",
                    "cancel_reason": "Compró en otra automotora la semana pasada",
                    "created_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/subscriptions/{id}/renew": {
      "post": {
        "summary": "Renew a Búsqueda — «Renovar 90 días»",
        "description": "The pre-expiry warning bell’s first action (ADR 0109 §5), and the same act from the list’s history on an already-expired want: the clock moves to NOW + 90 days, capped at the customer’s own deadline when they gave one and it falls earlier — never past it. Renewing an EXPIRED want re-opens it (`status: open`); renewing an OPEN one just extends it. Either way the pre-expiry warning is re-armed for the new window. No body. A want that is `fulfilled` or `cancelled` is a 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The renewed Búsqueda",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "status": "open",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "fire_at": null,
                    "fired_at": null,
                    "fired_channel": null,
                    "pending_event": null,
                    "pending_fire_at": null,
                    "pending_meta": null,
                    "pending_reason": null,
                    "preferred_channel": "whatsapp",
                    "created_by_kind": "human_user",
                    "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfiller_kind": "ai_agent",
                    "fulfiller_user_id": null,
                    "consent_message_id": null,
                    "outbound_action_id": null,
                    "satisfied_by_message_id": null,
                    "failure_reason": null,
                    "terminal_reason": null,
                    "defer_count": 0,
                    "notice_count": 0,
                    "notice_failure_notified_cause": null,
                    "last_notice_at": null,
                    "last_notice_unkept_at": null,
                    "busqueda_swept_at": null,
                    "expiry_warned_at": null,
                    "expires_at": "2027-01-12T16:00:00.000Z",
                    "customer_deadline_at": null,
                    "fulfilled_at": null,
                    "fulfilled_by": null,
                    "fulfilled_vehicle_id": null,
                    "fulfilment_source": null,
                    "cancelled_at": null,
                    "cancelled_by": null,
                    "cancelled_by_kind": null,
                    "cancel_reason_code": null,
                    "cancel_reason": null,
                    "created_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/subscriptions/{id}/let-expire": {
      "post": {
        "summary": "Decline to renew a Búsqueda — «Dejar vencer»",
        "description": "The pre-expiry warning bell’s second action (ADR 0109 §5): the owner looked and chose not to renew. The want’s status and clock are untouched — it stays `open` and the existing TTL sweep still closes it on schedule; this only records the deliberate choice, distinct from a want that lapses with nobody having looked. No body. Only an OPEN want can be declined (a 409 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Búsqueda, unchanged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "followup_id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "contact_id": "7a7a7a7a-0000-4000-8000-000000000030",
                    "conversation_id": "7a7a7a7a-0000-4000-8000-000000000040",
                    "status": "open",
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "fire_at": null,
                    "fired_at": null,
                    "fired_channel": null,
                    "pending_event": null,
                    "pending_fire_at": null,
                    "pending_meta": null,
                    "pending_reason": null,
                    "preferred_channel": "whatsapp",
                    "created_by_kind": "human_user",
                    "created_by_id": "7a7a7a7a-0000-4000-8000-000000000060",
                    "fulfiller_kind": "ai_agent",
                    "fulfiller_user_id": null,
                    "consent_message_id": null,
                    "outbound_action_id": null,
                    "satisfied_by_message_id": null,
                    "failure_reason": null,
                    "terminal_reason": null,
                    "defer_count": 0,
                    "notice_count": 0,
                    "notice_failure_notified_cause": null,
                    "last_notice_at": null,
                    "last_notice_unkept_at": null,
                    "busqueda_swept_at": null,
                    "expiry_warned_at": null,
                    "expires_at": "2027-01-08T14:00:00.000Z",
                    "customer_deadline_at": null,
                    "fulfilled_at": null,
                    "fulfilled_by": null,
                    "fulfilled_vehicle_id": null,
                    "fulfilment_source": null,
                    "cancelled_at": null,
                    "cancelled_by": null,
                    "cancelled_by_kind": null,
                    "cancel_reason_code": null,
                    "cancel_reason": null,
                    "created_at": "2026-10-10T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/subscriptions/{id}": {
      "get": {
        "summary": "One Búsqueda, in full (ADR 0109 §4)",
        "description": "The lead ficha, the vehicle panel’s «Abrir búsqueda» and a workspace-list row all open into this: the contact, the resolved owner, the criteria decomposed into display labels, days left, every car the (Búsqueda, vehicle) pair ledger has already paired (what pre-fills the fulfil picker), and — once closed — the fulfilment or cancellation record. A timer Seguimiento here is a 400 (this view is a Búsqueda’s). `contact.name` / `contact.phone` are further gated on `contacts:read` — absent, not null, without it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Búsqueda, in full",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000020",
                    "status": "open",
                    "trigger_key": "vehicle_in_stock",
                    "contact": {
                      "id": "7a7a7a7a-0000-4000-8000-000000000030",
                      "name": "María González",
                      "phone": "+56 9 1234 5678"
                    },
                    "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                    "owner": {
                      "user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                      "name": "Carlos Pérez",
                      "avatar_url": null
                    },
                    "criteria": {
                      "make": "Mazda",
                      "model": "CX-5",
                      "year_min": 2020,
                      "max_price": 18000000
                    },
                    "note": null,
                    "summary": {
                      "name": "Mazda CX-5",
                      "years": "2020 en adelante",
                      "budget": "hasta $18.000.000",
                      "km": null,
                      "soughtLabel": "Mazda CX-5 · 2020 en adelante",
                      "summary": "Mazda CX-5 · 2020 en adelante · hasta $18.000.000",
                      "groupKey": "mazda cx-5|2020|"
                    },
                    "expires_at": "2027-01-08T14:00:00.000Z",
                    "customer_deadline_at": null,
                    "days_left": 90,
                    "notice_count": 1,
                    "last_notice_at": "2026-10-12T15:00:00.000Z",
                    "notified_vehicles": [
                      {
                        "vehicle_id": "7a7a7a7a-0000-4000-8000-000000000070",
                        "make": "Mazda",
                        "model": "CX-5",
                        "year": 2021,
                        "registration_number": "KRTZ45",
                        "verdict": "calza",
                        "state": "notified",
                        "notified_at": "2026-10-12T15:00:00.000Z",
                        "dismissed_at": null
                      }
                    ],
                    "fulfilled_at": null,
                    "fulfilled_vehicle": null,
                    "fulfilment_source": null,
                    "cancelled_at": null,
                    "cancel_reason_code": null,
                    "cancel_reason": null,
                    "cancelled_by_kind": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Cancel one waiting subscription",
        "description": "Cancels this contact’s subscription only; the recipe is untouched. `id` is a subscription.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/followups/busquedas": {
      "get": {
        "summary": "Open wants, grouped by what is sought",
        "description": "Tenant-wide (not visibility-gated — a shared demand worklist, the same divergence `GET /vehicles/{id}/busquedas` documents), including a want whose contact has no lead at all. Grouped by name + year bounds (\"Porsche 911 · 2016 en adelante\"), each group counting DISTINCT customers. `seller_id` filters on the resolved owner (the contact’s open lead’s owner, else the origin conversation’s assignee); `expires_within` keeps only wants whose clock is at most 7, 30 or 90 days out. `notified_from` + `notified_to` (ISO instants, both or neither) keep only búsquedas whose customer notice settled in that window — exactly what a daily Búsquedas summary counted: `notice=sent` reached the customer (a notified vehicle pair in the window), `notice=failed` could not be sent, omitted = either. `status=any` lists every status (fulfilled, expired, cancelled), not only open wants; each row carries its `status`. `contact.name` / `contact.phone` are further gated on `contacts:read` — absent, not null, without it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "seller_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "7",
                "30",
                "90"
              ]
            },
            "required": false,
            "name": "expires_within",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "notified_from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "notified_to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "sent",
                "failed"
              ]
            },
            "required": false,
            "name": "notice",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "any"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Open Búsqueda groups",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "group_key": "mazda cx-5|2020|",
                      "sought_label": "Mazda CX-5 · 2020 en adelante",
                      "count": 1,
                      "rows": [
                        {
                          "subscription_id": "7a7a7a7a-0000-4000-8000-000000000020",
                          "status": "open",
                          "contact": {
                            "id": "7a7a7a7a-0000-4000-8000-000000000030",
                            "name": "María González",
                            "phone": "+56 9 1234 5678"
                          },
                          "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                          "owner": {
                            "user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                            "name": "Carlos Pérez",
                            "avatar_url": null
                          },
                          "note": null,
                          "expires_at": "2027-01-08T14:00:00.000Z",
                          "customer_deadline_at": null,
                          "days_left": 90,
                          "last_notice_at": "2026-10-12T15:00:00.000Z",
                          "last_notice_vehicle": {
                            "vehicle_id": "7a7a7a7a-0000-4000-8000-000000000070",
                            "make": "Mazda",
                            "model": "CX-5",
                            "year": 2021,
                            "registration_number": "KRTZ45"
                          }
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/busquedas/history": {
      "get": {
        "summary": "The «Historial» tab — fulfilled + expired Búsquedas",
        "description": "Never `cancelled`: a seller-withdrawn want is not buying intelligence the same way a fulfilled or an expired one is. Each `fulfilled` row names the car that answered it. `contact.name` / `contact.phone` are further gated on `contacts:read` — absent, not null, without it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "seller_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "fulfilled",
                "expired"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Closed Búsquedas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "subscription_id": "7a7a7a7a-0000-4000-8000-000000000020",
                      "status": "fulfilled",
                      "contact": {
                        "id": "7a7a7a7a-0000-4000-8000-000000000030",
                        "name": "María González",
                        "phone": "+56 9 1234 5678"
                      },
                      "lead_id": "7a7a7a7a-0000-4000-8000-000000000050",
                      "owner": {
                        "user_id": "7a7a7a7a-0000-4000-8000-000000000060",
                        "name": "Carlos Pérez",
                        "avatar_url": null
                      },
                      "sought_label": "Mazda CX-5 · 2020 en adelante",
                      "closed_at": "2026-10-14T16:00:00.000Z",
                      "fulfilled_vehicle": {
                        "vehicle_id": "7a7a7a7a-0000-4000-8000-000000000070",
                        "make": "Mazda",
                        "model": "CX-5",
                        "year": 2021,
                        "registration_number": "KRTZ45"
                      },
                      "fulfilment_source": "manual"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/{id}": {
      "get": {
        "summary": "Get one follow-up recipe",
        "description": "The recipe row itself: `name`, the agent-facing `description`, `trigger_key`, `active` and `origin`. It carries no counters — use `GET /followups` for `waiting_count` / `kept_count` and `GET /followups/{id}/waiting-count` for the open subscriptions. 404 when the id is not a recipe of this workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The recipe",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000010",
                    "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "name": "Avisar cuando llegue el auto que busca",
                    "description": "Ofrécelo cuando el cliente pide un auto que no está en stock. Pregunta marca, modelo y presupuesto antes de suscribirlo.",
                    "trigger_key": "vehicle_in_stock",
                    "active": true,
                    "origin": "platform",
                    "created_at": "2026-10-01T14:00:00.000Z",
                    "updated_at": "2026-10-01T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Edit a follow-up recipe",
        "description": "Partial patch; an empty body is a 400. Editing does not touch subscriptions already waiting on it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "trigger_key": {
                    "type": "string",
                    "enum": [
                      "vehicle_in_stock",
                      "vehicle_price_drop",
                      "appointment_slot_available",
                      "vehicle_unavailable",
                      "payment_received",
                      "payment_partial",
                      "obligation_paid",
                      "payment_overdue",
                      "pack_sessions_low",
                      "pack_expiring",
                      "birthday",
                      "booking_abandoned",
                      "after_delay"
                    ]
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "name": "Avisar cuando llegue el auto",
                "active": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated recipe",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "followup": {
                      "id": "7a7a7a7a-0000-4000-8000-000000000010",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "name": "Avisar cuando llegue el auto",
                      "description": "Ofrécelo cuando el cliente pide un auto que no está en stock. Pregunta marca, modelo y presupuesto antes de suscribirlo.",
                      "trigger_key": "vehicle_in_stock",
                      "active": true,
                      "origin": "platform",
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-01T14:00:00.000Z"
                    },
                    "cancelled_subscriptions": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a follow-up recipe",
        "description": "Requires the recipe to be inactive — deleting an active one is a 409, so the subscription cancellation is always a deliberate separate step.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/followups/{id}/waiting-count": {
      "get": {
        "summary": "How many subscriptions are waiting on this recipe",
        "description": "Exists so the deactivate/delete confirmation can be honest about what it is about to cancel — read it before the destructive call.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ waiting }` — open subscription count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "waiting": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/{id}/waiting": {
      "get": {
        "summary": "Who this recipe is still to tell",
        "description": "The people behind the recipe’s `waiting_count` («2 clientes por avisar»): its open subscriptions, the same ones the count counts, soonest aviso first and then the oldest want. Each person carries `subscription_id`, `contact_id`, `contact_name`, `conversation_id` and `conversation_display_id` (the thread to open), `fire_at` and `fire_at_label` («dom 25 oct · 09:00», the workspace’s timezone; null for a Búsqueda, which waits for the stock), `created_at` and `expires_at`. `total` is the full count; `truncated` is true when more than the 200 listed are waiting.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ total, truncated, people }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 2,
                    "truncated": false,
                    "people": [
                      {
                        "subscription_id": "6f1c2a40-1b7e-4c55-9a2e-3d0f8e1b2c11",
                        "contact_id": "0b3e9d7a-5c21-4f8e-8a14-77c2d9e4f0a3",
                        "contact_name": "Camila Rojas",
                        "conversation_id": "9a7d3c10-2e4b-4f61-b8c5-1e0f2d3a4b5c",
                        "conversation_display_id": "C-312",
                        "fire_at": "2026-10-26T12:00:00.000Z",
                        "fire_at_label": "lun 26 oct · 09:00",
                        "created_at": "2026-10-22T15:04:00.000Z",
                        "expires_at": "2026-11-21T15:04:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/followups/{id}/active": {
      "post": {
        "summary": "Activate or deactivate a recipe",
        "description": "Deactivating **cancels every open subscription** and the response reports how many — this is not a pause, and reactivating does not bring them back. Check `/waiting-count` first.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Follow-ups"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "active"
                ]
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The recipe plus the number of subscriptions cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "followup": {
                      "id": "7a7a7a7a-0000-4000-8000-000000000010",
                      "tenant_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "name": "Avisar cuando llegue el auto que busca",
                      "description": "Ofrécelo cuando el cliente pide un auto que no está en stock. Pregunta marca, modelo y presupuesto antes de suscribirlo.",
                      "trigger_key": "vehicle_in_stock",
                      "active": false,
                      "origin": "platform",
                      "created_at": "2026-10-01T14:00:00.000Z",
                      "updated_at": "2026-10-01T14:00:00.000Z"
                    },
                    "cancelled_subscriptions": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences": {
      "get": {
        "summary": "List the workspace’s sequences",
        "description": "Every sequence of the workspace, lowest `priority` first (an agreed reminder wins over the others), each with its whole graph and the numbers of the last 30 days. Each sequence carries `situations` (its lanes: «Conversó y dejó de responder», «Nunca respondió», «Ya está perdido» for `builtin:si-no-responde`; one per lead source for `builtin:al-llegar`; one per recipe for `builtin:avisos`), `steps` (channel, the configured `wait`, the drawn `wait_after_previous` and `at_hours`, `mode` as configured and `mode_effective`, the config block it comes from in `config_ref`, and `stats`: `waiting_now`, `sent_30d`, `reply_rate_30d`, `daily_14d`), `connections` between step keys and the `entry` / `exit` / `end:<situation>` nodes (`reply_exit` is «si responde»), `window_markers` (where WhatsApp’s 24 h window closes; from there only approved templates) and `ends` (mark lost, finish, or continue in another sequence). Order, gaps and markers of «Si no responde» come from the same simulation `GET /followups/recontacto/plan` renders.\n\nA workspace with nothing configured still lists the three built-ins, `status: paused`. `lang` picks the labels; omitted = the workspace’s language. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The sequences",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sequence"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "42bcd0a4-e0bd-5418-8a00-71495a4d3e46",
                      "key": "builtin:avisos",
                      "kind": "builtin",
                      "name": "Avisos",
                      "description": "Lo que el cliente pidió que le recordáramos.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_asked_reminder",
                        "filters": [
                          {
                            "field": "trigger",
                            "op": "in",
                            "values": [
                              "after_delay"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "promise_kept",
                        "label": "Cumplir lo que le prometimos"
                      },
                      "agent_id": null,
                      "priority": 1,
                      "situations": [
                        {
                          "key": "33333333-3333-4333-8333-333333333333",
                          "name": "Recordatorio acordado",
                          "enabled": true,
                          "first_step": "followup-33333333-3333-4333-8333-333333333333",
                          "waiting_now": 10,
                          "notes": []
                        }
                      ],
                      "steps": [
                        {
                          "id": "15c4a32c-b151-589d-ad47-25181454816b",
                          "key": "followup-33333333-3333-4333-8333-333333333333",
                          "situation": "33333333-3333-4333-8333-333333333333",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "agreed_time"
                          },
                          "wait_after_previous": null,
                          "at_hours": null,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "promise",
                            "followup_id": "33333333-3333-4333-8333-333333333333",
                            "trigger_key": "after_delay"
                          },
                          "config_ref": {
                            "type": "followup",
                            "followup_id": "33333333-3333-4333-8333-333333333333",
                            "trigger_key": "after_delay"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 10,
                            "sent_30d": 14,
                            "reply_rate_30d": 0.429,
                            "daily_14d": [
                              0,
                              1,
                              1,
                              0,
                              2,
                              1,
                              0,
                              1,
                              1,
                              2,
                              1,
                              0,
                              1,
                              3
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "followup-33333333-3333-4333-8333-333333333333",
                          "kind": "next",
                          "situation": "33333333-3333-4333-8333-333333333333"
                        },
                        {
                          "from": "followup-33333333-3333-4333-8333-333333333333",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "33333333-3333-4333-8333-333333333333"
                        },
                        {
                          "from": "followup-33333333-3333-4333-8333-333333333333",
                          "to": "end:33333333-3333-4333-8333-333333333333",
                          "kind": "end",
                          "situation": "33333333-3333-4333-8333-333333333333"
                        }
                      ],
                      "window_markers": [],
                      "ends": [
                        {
                          "key": "end:33333333-3333-4333-8333-333333333333",
                          "situation": "33333333-3333-4333-8333-333333333333",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Avisado",
                          "detail": null,
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 10,
                        "sent_30d": 14,
                        "reply_rate_30d": 0.429,
                        "daily_14d": [
                          0,
                          1,
                          1,
                          0,
                          2,
                          1,
                          0,
                          1,
                          1,
                          2,
                          1,
                          0,
                          1,
                          3
                        ]
                      }
                    },
                    {
                      "id": "01f68f1c-696b-5fc5-8191-d26ac3a3a8ee",
                      "key": "builtin:al-llegar",
                      "kind": "builtin",
                      "name": "Al llegar",
                      "description": "Un lead que no abrió el chat recibe el primer mensaje.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "lead_arrived",
                        "filters": [
                          {
                            "field": "source",
                            "op": "in",
                            "values": [
                              "website"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente responda"
                      },
                      "agent_id": null,
                      "priority": 2,
                      "situations": [
                        {
                          "key": "website",
                          "name": "Lead del sitio web",
                          "enabled": true,
                          "first_step": "first_touch-website",
                          "waiting_now": null,
                          "notes": []
                        }
                      ],
                      "steps": [
                        {
                          "id": "168d38eb-62b8-5ecc-b0cb-663736f9be97",
                          "key": "first_touch-website",
                          "situation": "website",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "minutes",
                            "from": "start",
                            "from_step": null,
                            "hours": 0.08333333333333333
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "minutes"
                          },
                          "at_hours": 0.08333333333333333,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "template_ladder",
                            "templates": [
                              {
                                "name": "cotizacion_web",
                                "language": "es"
                              }
                            ]
                          },
                          "config_ref": {
                            "type": "first_touch",
                            "source": "website",
                            "config_origin": "configured"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": null,
                            "sent_30d": 322,
                            "reply_rate_30d": 0.304,
                            "daily_14d": [
                              10,
                              12,
                              11,
                              13,
                              12,
                              11,
                              12,
                              13,
                              10,
                              11,
                              12,
                              13,
                              11,
                              12
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "first_touch-website",
                          "kind": "next",
                          "situation": "website"
                        },
                        {
                          "from": "first_touch-website",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "website"
                        },
                        {
                          "from": "first_touch-website",
                          "to": "end:website",
                          "kind": "end",
                          "situation": "website"
                        }
                      ],
                      "window_markers": [],
                      "ends": [
                        {
                          "key": "end:website",
                          "situation": "website",
                          "type": "continue",
                          "after": null,
                          "at_hours": null,
                          "label": "Si no responde, sigue en «Nunca respondió»",
                          "detail": null,
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "never_replied"
                          }
                        }
                      ],
                      "stats": {
                        "waiting_now": null,
                        "sent_30d": 322,
                        "reply_rate_30d": 0.304,
                        "daily_14d": [
                          10,
                          12,
                          11,
                          13,
                          12,
                          11,
                          12,
                          13,
                          10,
                          11,
                          12,
                          13,
                          11,
                          12
                        ]
                      }
                    },
                    {
                      "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                      "key": "builtin:si-no-responde",
                      "kind": "builtin",
                      "name": "Si no responde",
                      "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente vuelva a responder"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "situations": [
                        {
                          "key": "conversed",
                          "name": "Conversó y dejó de responder",
                          "enabled": true,
                          "first_step": "unanswered_message-1",
                          "waiting_now": 45,
                          "notes": []
                        },
                        {
                          "key": "never_replied",
                          "name": "Nunca respondió",
                          "enabled": true,
                          "first_step": "never_replied-1",
                          "waiting_now": 1,
                          "notes": []
                        },
                        {
                          "key": "lost",
                          "name": "Ya está perdido",
                          "enabled": true,
                          "first_step": "lost-1",
                          "waiting_now": 9,
                          "notes": [
                            "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                          ]
                        }
                      ],
                      "steps": [
                        {
                          "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "wait_after_previous": {
                            "value": 4,
                            "unit": "hours"
                          },
                          "at_hours": 4,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 18,
                            "sent_30d": 212,
                            "reply_rate_30d": 0.231,
                            "daily_14d": [
                              6,
                              8,
                              7,
                              9,
                              8,
                              7,
                              8,
                              9,
                              6,
                              7,
                              8,
                              9,
                              7,
                              8
                            ]
                          }
                        },
                        {
                          "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "hours"
                          },
                          "at_hours": 34,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [
                            "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                          ],
                          "stats": {
                            "waiting_now": 11,
                            "sent_30d": 164,
                            "reply_rate_30d": 0.14,
                            "daily_14d": [
                              5,
                              6,
                              5,
                              6,
                              5,
                              6,
                              5,
                              6,
                              5,
                              6,
                              5,
                              6,
                              5,
                              6
                            ]
                          }
                        },
                        {
                          "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 14,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 9,
                            "sent_30d": 120,
                            "reply_rate_30d": 0.092,
                            "daily_14d": [
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4,
                              4
                            ]
                          }
                        },
                        {
                          "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 7,
                            "sent_30d": 46,
                            "reply_rate_30d": 0.065,
                            "daily_14d": [
                              1,
                              2,
                              1,
                              2,
                              1,
                              2,
                              1,
                              2,
                              1,
                              2,
                              1,
                              2,
                              1,
                              2
                            ]
                          }
                        },
                        {
                          "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "hours"
                          },
                          "at_hours": 3,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [
                            "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                          ],
                          "stats": {
                            "waiting_now": 1,
                            "sent_30d": 92,
                            "reply_rate_30d": 0.076,
                            "daily_14d": [
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3,
                              3
                            ]
                          }
                        },
                        {
                          "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "hours"
                          },
                          "at_hours": 8,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "wait_after_previous": {
                            "value": 40,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "days"
                          },
                          "at_hours": 720,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 9,
                            "sent_30d": 27,
                            "reply_rate_30d": 0.185,
                            "daily_14d": [
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1,
                              1
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": "conversed",
                          "after_step": "unanswered_message-1",
                          "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Fin de este camino.",
                          "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 55,
                        "sent_30d": 661,
                        "reply_rate_30d": 0.148,
                        "daily_14d": [
                          20,
                          24,
                          21,
                          25,
                          22,
                          23,
                          22,
                          25,
                          20,
                          23,
                          22,
                          25,
                          21,
                          24
                        ]
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a custom sequence",
        "description": "A custom sequence is the workspace’s own: who enters (`entry`: an event — `lead_arrived`, `customer_went_quiet`, `stage_changed`, `quoted_not_booked`, `appointment_booked`; `signed_up_not_bought` and `installment_overdue` are accepted but cannot be published yet; or a `list`, enrolled with consent through `POST /sequences/{id}/enrollments/from-list` — with filters on `interest`, `source`, `stage` or `tag`), the `goal` that makes a person leave when met (`reply`, `books_visit`, `books_demo`; `pays` cannot be published yet) and the steps, edited as a draft and run once published (`POST /sequences/{id}/publish`). A person is in ONE sequence at a time among «Si no responde» and the custom ones, and enters the highest-priority one they qualify for; «Al llegar» and «Avisos» stay outside that rule. The engine sends through the same guards as «Si no responde»: contact hours, per-person limits, opt-out, the 24 h WhatsApp window, pauses and «Con tu OK».\n\nCreates the sequence from `template` (a key from `GET /sequences/templates`; its suggested steps become the first draft) or from scratch (`blank`), as a DRAFT: `status: draft`, nothing runs until it is published. `name`, `entry` and `goal` override the template’s. It goes to the bottom of the priority list. Every suggested step starts as «Con tu OK» (`approval`). Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequenceCreateRequest"
              },
              "example": {
                "template": "quote_followup",
                "name": "Seguimiento de cotización"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new sequence and its draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceCreateResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence": {
                      "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "kind": "custom",
                      "name": "Seguimiento de cotización",
                      "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                      "status": "draft",
                      "entry": {
                        "type": "event",
                        "event": "quoted_not_booked",
                        "filters": []
                      },
                      "goal": {
                        "type": "books_visit",
                        "label": "Agenda visita"
                      },
                      "agent_id": null,
                      "collection": false,
                      "priority": 4,
                      "situations": [],
                      "steps": [
                        {
                          "id": "affce008-6fdb-52eb-8273-329b6da03c90",
                          "key": "step-1",
                          "situation": null,
                          "position": 1,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 72,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        },
                        {
                          "id": "fed60c11-4a4e-50dd-adbd-bdc25a39fc35",
                          "key": "step-2",
                          "situation": null,
                          "position": 2,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 24
                          },
                          "wait_after_previous": {
                            "value": 1,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        },
                        {
                          "id": "81573e28-456d-5a69-9c16-b7e2082b589a",
                          "key": "step-3",
                          "situation": null,
                          "position": 3,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 24
                          },
                          "wait_after_previous": {
                            "value": 1,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "step-1",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-1",
                          "to": "step-2",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-2",
                          "to": "step-3",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-3",
                          "to": "end",
                          "kind": "end",
                          "situation": null
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": null,
                          "after_step": null,
                          "label": "Pasadas las 24 h, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end",
                          "situation": null,
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Termina",
                          "detail": null,
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": null,
                      "draft": {
                        "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                        "change_count": 6,
                        "updated_at": "2026-11-02T15:00:00.000Z"
                      }
                    },
                    "draft": {
                      "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "sequence_key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                      "based_on_version": null,
                      "change_count": 6,
                      "created_at": "2026-11-02T15:00:00.000Z",
                      "updated_at": "2026-11-02T15:00:00.000Z",
                      "created_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "updated_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222"
                      },
                      "definition": {
                        "schema_version": 1,
                        "name": "Seguimiento de cotización",
                        "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                        "entry": {
                          "type": "event",
                          "event": "quoted_not_booked",
                          "filters": []
                        },
                        "goal": {
                          "type": "books_visit"
                        },
                        "agent_id": null,
                        "priority": 0,
                        "situations": [],
                        "steps": [
                          {
                            "key": "step-1",
                            "situation": null,
                            "kind": "message",
                            "position": 1,
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 3,
                              "unit": "days",
                              "from": "start",
                              "from_step": null,
                              "hours": 72
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          },
                          {
                            "key": "step-2",
                            "situation": null,
                            "kind": "message",
                            "position": 2,
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-1",
                              "hours": 24
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          },
                          {
                            "key": "step-3",
                            "situation": null,
                            "kind": "message",
                            "position": 3,
                            "channel": "call",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-2",
                              "hours": 24
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          }
                        ],
                        "connections": [
                          {
                            "from": "entry",
                            "to": "step-1",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "step-2",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "step-3",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "end",
                            "kind": "end",
                            "situation": null
                          }
                        ],
                        "ends": [
                          {
                            "key": "end",
                            "situation": null,
                            "type": "finish",
                            "after": null,
                            "continues_in": null
                          }
                        ],
                        "settings": {
                          "type": "none"
                        }
                      },
                      "sequence": {
                        "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "kind": "custom",
                        "name": "Seguimiento de cotización",
                        "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                        "status": "draft",
                        "entry": {
                          "type": "event",
                          "event": "quoted_not_booked",
                          "filters": []
                        },
                        "goal": {
                          "type": "books_visit",
                          "label": "Agenda visita"
                        },
                        "agent_id": null,
                        "collection": false,
                        "priority": 4,
                        "situations": [],
                        "steps": [
                          {
                            "id": "affce008-6fdb-52eb-8273-329b6da03c90",
                            "key": "step-1",
                            "situation": null,
                            "position": 1,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 3,
                              "unit": "days",
                              "from": "start",
                              "from_step": null,
                              "hours": 72
                            },
                            "wait_after_previous": {
                              "value": 3,
                              "unit": "days"
                            },
                            "at_hours": 72,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": true,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          },
                          {
                            "id": "fed60c11-4a4e-50dd-adbd-bdc25a39fc35",
                            "key": "step-2",
                            "situation": null,
                            "position": 2,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-1",
                              "hours": 24
                            },
                            "wait_after_previous": {
                              "value": 1,
                              "unit": "days"
                            },
                            "at_hours": 96,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": true,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          },
                          {
                            "id": "81573e28-456d-5a69-9c16-b7e2082b589a",
                            "key": "step-3",
                            "situation": null,
                            "position": 3,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "call",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-2",
                              "hours": 24
                            },
                            "wait_after_previous": {
                              "value": 1,
                              "unit": "days"
                            },
                            "at_hours": 120,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": false,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          }
                        ],
                        "connections": [
                          {
                            "from": "entry",
                            "to": "step-1",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "step-2",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "step-3",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "end",
                            "kind": "end",
                            "situation": null
                          }
                        ],
                        "window_markers": [
                          {
                            "situation": null,
                            "after_step": null,
                            "label": "Pasadas las 24 h, solo plantillas aprobadas"
                          }
                        ],
                        "ends": [
                          {
                            "key": "end",
                            "situation": null,
                            "type": "finish",
                            "after": null,
                            "at_hours": null,
                            "label": "Termina",
                            "detail": null,
                            "continues_in": null
                          }
                        ],
                        "stats": {
                          "waiting_now": 0,
                          "sent_30d": 0,
                          "reply_rate_30d": null,
                          "daily_14d": [
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0
                          ]
                        },
                        "published_version": null,
                        "draft": {
                          "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                          "change_count": 6,
                          "updated_at": "2026-11-02T15:00:00.000Z"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}": {
      "get": {
        "summary": "Read one sequence",
        "description": "One sequence by its uuid or by its key (`builtin:al-llegar`, `builtin:si-no-responde`, `builtin:avisos`). Each sequence carries `situations` (its lanes: «Conversó y dejó de responder», «Nunca respondió», «Ya está perdido» for `builtin:si-no-responde`; one per lead source for `builtin:al-llegar`; one per recipe for `builtin:avisos`), `steps` (channel, the configured `wait`, the drawn `wait_after_previous` and `at_hours`, `mode` as configured and `mode_effective`, the config block it comes from in `config_ref`, and `stats`: `waiting_now`, `sent_30d`, `reply_rate_30d`, `daily_14d`), `connections` between step keys and the `entry` / `exit` / `end:<situation>` nodes (`reply_exit` is «si responde»), `window_markers` (where WhatsApp’s 24 h window closes; from there only approved templates) and `ends` (mark lost, finish, or continue in another sequence). Order, gaps and markers of «Si no responde» come from the same simulation `GET /followups/recontacto/plan` renders.\n\nA built-in’s uuid is stable for the workspace. 404 for any other id. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The sequence",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Sequence"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "key": "builtin:si-no-responde",
                    "kind": "builtin",
                    "name": "Si no responde",
                    "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                    "status": "active",
                    "entry": {
                      "type": "event",
                      "event": "customer_went_quiet",
                      "filters": [
                        {
                          "field": "situation",
                          "op": "in",
                          "values": [
                            "conversed",
                            "never_replied",
                            "lost"
                          ]
                        }
                      ]
                    },
                    "goal": {
                      "type": "reply",
                      "label": "Que el cliente vuelva a responder"
                    },
                    "agent_id": null,
                    "priority": 3,
                    "situations": [
                      {
                        "key": "conversed",
                        "name": "Conversó y dejó de responder",
                        "enabled": true,
                        "first_step": "unanswered_message-1",
                        "waiting_now": 45,
                        "notes": []
                      },
                      {
                        "key": "never_replied",
                        "name": "Nunca respondió",
                        "enabled": true,
                        "first_step": "never_replied-1",
                        "waiting_now": 1,
                        "notes": []
                      },
                      {
                        "key": "lost",
                        "name": "Ya está perdido",
                        "enabled": true,
                        "first_step": "lost-1",
                        "waiting_now": 9,
                        "notes": [
                          "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                        ]
                      }
                    ],
                    "steps": [
                      {
                        "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                        "key": "unanswered_message-1",
                        "situation": "conversed",
                        "kind": "message",
                        "position": 1,
                        "channel": "same_chat",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 4,
                          "unit": "hours",
                          "from": "start",
                          "from_step": null,
                          "hours": 4
                        },
                        "wait_after_previous": {
                          "value": 4,
                          "unit": "hours"
                        },
                        "at_hours": 4,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": false,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "unanswered_message",
                          "step": 1,
                          "after_hours": 4,
                          "unit": null,
                          "autonomy": "auto",
                          "channel": null,
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 18,
                          "sent_30d": 212,
                          "reply_rate_30d": 0.231,
                          "daily_14d": [
                            6,
                            8,
                            7,
                            9,
                            8,
                            7,
                            8,
                            9,
                            6,
                            7,
                            8,
                            9,
                            7,
                            8
                          ]
                        }
                      },
                      {
                        "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                        "key": "unanswered_message-2",
                        "situation": "conversed",
                        "kind": "message",
                        "position": 2,
                        "channel": "same_chat",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 30,
                          "unit": "hours",
                          "from": "previous_step",
                          "from_step": "unanswered_message-1",
                          "hours": 30
                        },
                        "wait_after_previous": {
                          "value": 30,
                          "unit": "hours"
                        },
                        "at_hours": 34,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "unanswered_message",
                          "step": 2,
                          "after_hours": 30,
                          "unit": null,
                          "autonomy": "auto",
                          "channel": null,
                          "min_score": null
                        },
                        "warnings": [
                          "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                        ],
                        "stats": {
                          "waiting_now": 11,
                          "sent_30d": 164,
                          "reply_rate_30d": 0.14,
                          "daily_14d": [
                            5,
                            6,
                            5,
                            6,
                            5,
                            6,
                            5,
                            6,
                            5,
                            6,
                            5,
                            6,
                            5,
                            6
                          ]
                        }
                      },
                      {
                        "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                        "key": "stalled-1",
                        "situation": "conversed",
                        "kind": "message",
                        "position": 3,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 2,
                          "unit": "days",
                          "from": "start",
                          "from_step": null,
                          "hours": 48
                        },
                        "wait_after_previous": {
                          "value": 14,
                          "unit": "hours"
                        },
                        "at_hours": 48,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "stalled",
                          "step": 1,
                          "after_hours": 48,
                          "unit": "d",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 9,
                          "sent_30d": 120,
                          "reply_rate_30d": 0.092,
                          "daily_14d": [
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4,
                            4
                          ]
                        }
                      },
                      {
                        "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                        "key": "stalled-2",
                        "situation": "conversed",
                        "kind": "message",
                        "position": 4,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 3,
                          "unit": "days",
                          "from": "previous_step",
                          "from_step": "stalled-1",
                          "hours": 72
                        },
                        "wait_after_previous": {
                          "value": 3,
                          "unit": "days"
                        },
                        "at_hours": 120,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "stalled",
                          "step": 2,
                          "after_hours": 72,
                          "unit": "d",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 7,
                          "sent_30d": 46,
                          "reply_rate_30d": 0.065,
                          "daily_14d": [
                            1,
                            2,
                            1,
                            2,
                            1,
                            2,
                            1,
                            2,
                            1,
                            2,
                            1,
                            2,
                            1,
                            2
                          ]
                        }
                      },
                      {
                        "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                        "key": "never_replied-1",
                        "situation": "never_replied",
                        "kind": "message",
                        "position": 1,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 3,
                          "unit": "hours",
                          "from": "start",
                          "from_step": null,
                          "hours": 3
                        },
                        "wait_after_previous": {
                          "value": 3,
                          "unit": "hours"
                        },
                        "at_hours": 3,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "never_replied",
                          "step": 1,
                          "after_hours": 3,
                          "unit": "h",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [
                          "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                        ],
                        "stats": {
                          "waiting_now": 1,
                          "sent_30d": 92,
                          "reply_rate_30d": 0.076,
                          "daily_14d": [
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3,
                            3
                          ]
                        }
                      },
                      {
                        "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                        "key": "never_replied-2",
                        "situation": "never_replied",
                        "kind": "message",
                        "position": 2,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 5,
                          "unit": "hours",
                          "from": "previous_step",
                          "from_step": "never_replied-1",
                          "hours": 5
                        },
                        "wait_after_previous": {
                          "value": 5,
                          "unit": "hours"
                        },
                        "at_hours": 8,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "never_replied",
                          "step": 2,
                          "after_hours": 5,
                          "unit": "h",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 0,
                          "sent_30d": 0,
                          "reply_rate_30d": null,
                          "daily_14d": [
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0
                          ]
                        }
                      },
                      {
                        "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                        "key": "never_replied-3",
                        "situation": "never_replied",
                        "kind": "message",
                        "position": 3,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 40,
                          "unit": "hours",
                          "from": "previous_step",
                          "from_step": "never_replied-2",
                          "hours": 40
                        },
                        "wait_after_previous": {
                          "value": 40,
                          "unit": "hours"
                        },
                        "at_hours": 48,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "never_replied",
                          "step": 3,
                          "after_hours": 40,
                          "unit": "d",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 0,
                          "sent_30d": 0,
                          "reply_rate_30d": null,
                          "daily_14d": [
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0
                          ]
                        }
                      },
                      {
                        "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                        "key": "never_replied-4",
                        "situation": "never_replied",
                        "kind": "message",
                        "position": 4,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 3,
                          "unit": "days",
                          "from": "previous_step",
                          "from_step": "never_replied-3",
                          "hours": 72
                        },
                        "wait_after_previous": {
                          "value": 3,
                          "unit": "days"
                        },
                        "at_hours": 120,
                        "mode": "auto",
                        "mode_effective": "auto",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "never_replied",
                          "step": 4,
                          "after_hours": 72,
                          "unit": "d",
                          "autonomy": "auto",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 0,
                          "sent_30d": 0,
                          "reply_rate_30d": null,
                          "daily_14d": [
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0
                          ]
                        }
                      },
                      {
                        "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                        "key": "lost-1",
                        "situation": "lost",
                        "kind": "message",
                        "position": 1,
                        "channel": "whatsapp",
                        "fallback_channel": null,
                        "wait": {
                          "type": "duration",
                          "value": 30,
                          "unit": "days",
                          "from": "start",
                          "from_step": null,
                          "hours": 720
                        },
                        "wait_after_previous": {
                          "value": 30,
                          "unit": "days"
                        },
                        "at_hours": 720,
                        "mode": "approval",
                        "mode_effective": "approval",
                        "sends": true,
                        "not_sent_reason": null,
                        "needs_template": true,
                        "content": {
                          "type": "ai_message",
                          "template_after_window": true
                        },
                        "config_ref": {
                          "type": "recontacto",
                          "rail": "lost",
                          "step": 1,
                          "after_hours": 720,
                          "unit": "d",
                          "autonomy": "approval",
                          "channel": "whatsapp",
                          "min_score": null
                        },
                        "warnings": [],
                        "stats": {
                          "waiting_now": 9,
                          "sent_30d": 27,
                          "reply_rate_30d": 0.185,
                          "daily_14d": [
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1,
                            1
                          ]
                        }
                      }
                    ],
                    "connections": [
                      {
                        "from": "entry",
                        "to": "unanswered_message-1",
                        "kind": "next",
                        "situation": "conversed"
                      },
                      {
                        "from": "unanswered_message-1",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "conversed"
                      },
                      {
                        "from": "unanswered_message-1",
                        "to": "unanswered_message-2",
                        "kind": "next",
                        "situation": "conversed"
                      },
                      {
                        "from": "unanswered_message-2",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "conversed"
                      },
                      {
                        "from": "unanswered_message-2",
                        "to": "stalled-1",
                        "kind": "next",
                        "situation": "conversed"
                      },
                      {
                        "from": "stalled-1",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "conversed"
                      },
                      {
                        "from": "stalled-1",
                        "to": "stalled-2",
                        "kind": "next",
                        "situation": "conversed"
                      },
                      {
                        "from": "stalled-2",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "conversed"
                      },
                      {
                        "from": "stalled-2",
                        "to": "end:conversed",
                        "kind": "end",
                        "situation": "conversed"
                      },
                      {
                        "from": "entry",
                        "to": "never_replied-1",
                        "kind": "next",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-1",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-1",
                        "to": "never_replied-2",
                        "kind": "next",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-2",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-2",
                        "to": "never_replied-3",
                        "kind": "next",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-3",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-3",
                        "to": "never_replied-4",
                        "kind": "next",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-4",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "never_replied"
                      },
                      {
                        "from": "never_replied-4",
                        "to": "end:never_replied",
                        "kind": "end",
                        "situation": "never_replied"
                      },
                      {
                        "from": "entry",
                        "to": "lost-1",
                        "kind": "next",
                        "situation": "lost"
                      },
                      {
                        "from": "lost-1",
                        "to": "exit",
                        "kind": "reply_exit",
                        "situation": "lost"
                      },
                      {
                        "from": "lost-1",
                        "to": "end:lost",
                        "kind": "end",
                        "situation": "lost"
                      }
                    ],
                    "window_markers": [
                      {
                        "situation": "conversed",
                        "after_step": "unanswered_message-1",
                        "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                      }
                    ],
                    "ends": [
                      {
                        "key": "end:conversed",
                        "situation": "conversed",
                        "type": "mark_lost",
                        "after": {
                          "value": 7,
                          "unit": "days"
                        },
                        "at_hours": 288,
                        "label": "Lo marcamos perdido",
                        "detail": "Si sigue sin responder 7 días después del último mensaje.",
                        "continues_in": {
                          "sequence_key": "builtin:si-no-responde",
                          "situation": "lost"
                        }
                      },
                      {
                        "key": "end:never_replied",
                        "situation": "never_replied",
                        "type": "mark_lost",
                        "after": {
                          "value": 7,
                          "unit": "days"
                        },
                        "at_hours": 288,
                        "label": "Lo marcamos perdido",
                        "detail": "Si sigue sin responder 7 días después del último mensaje.",
                        "continues_in": {
                          "sequence_key": "builtin:si-no-responde",
                          "situation": "lost"
                        }
                      },
                      {
                        "key": "end:lost",
                        "situation": "lost",
                        "type": "finish",
                        "after": null,
                        "at_hours": null,
                        "label": "Fin de este camino.",
                        "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                        "continues_in": null
                      }
                    ],
                    "stats": {
                      "waiting_now": 55,
                      "sent_30d": 661,
                      "reply_rate_30d": 0.148,
                      "daily_14d": [
                        20,
                        24,
                        21,
                        25,
                        22,
                        23,
                        22,
                        25,
                        20,
                        23,
                        22,
                        25,
                        21,
                        24
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a custom sequence",
        "description": "Deletes a custom sequence. People still in it leave with reason `sequence_deleted` (`exited` says how many) and its queued messages are cancelled, never sent; its history stays. Built-ins cannot be deleted (404). Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "What was deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDeleteResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "deleted": true,
                    "exited": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a custom sequence’s name, entry, goal or agent",
        "description": "A custom sequence is the workspace’s own: who enters (`entry`: an event — `lead_arrived`, `customer_went_quiet`, `stage_changed`, `quoted_not_booked`, `appointment_booked`; `signed_up_not_bought` and `installment_overdue` are accepted but cannot be published yet; or a `list`, enrolled with consent through `POST /sequences/{id}/enrollments/from-list` — with filters on `interest`, `source`, `stage` or `tag`), the `goal` that makes a person leave when met (`reply`, `books_visit`, `books_demo`; `pays` cannot be published yet) and the steps, edited as a draft and run once published (`POST /sequences/{id}/publish`). A person is in ONE sequence at a time among «Si no responde» and the custom ones, and enters the highest-priority one they qualify for; «Al llegar» and «Avisos» stay outside that rule. The engine sends through the same guards as «Si no responde»: contact hours, per-person limits, opt-out, the 24 h WhatsApp window, pauses and «Con tu OK».\n\nWrites `name`, `description`, `entry` and `goal` and `agent_id` (the AI agent that runs it; 400 unless it is an active agent of the workspace, `null` = the workspace’s current agent) into the draft (opening one from the published version when there is none), so who enters a running sequence changes only when it is published. Before the first publish the list shows the new name at once. Built-ins: 404 (edit them through their draft). Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequencePatchRequest"
              },
              "example": {
                "name": "Cotizó y no vino a la tienda"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The sequence and its draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceCreateResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence": {
                      "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "kind": "custom",
                      "name": "Cotizó y no vino a la tienda",
                      "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                      "status": "draft",
                      "entry": {
                        "type": "event",
                        "event": "quoted_not_booked",
                        "filters": []
                      },
                      "goal": {
                        "type": "books_visit",
                        "label": "Agenda visita"
                      },
                      "agent_id": null,
                      "collection": false,
                      "priority": 4,
                      "situations": [],
                      "steps": [
                        {
                          "id": "affce008-6fdb-52eb-8273-329b6da03c90",
                          "key": "step-1",
                          "situation": null,
                          "position": 1,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 72,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        },
                        {
                          "id": "fed60c11-4a4e-50dd-adbd-bdc25a39fc35",
                          "key": "step-2",
                          "situation": null,
                          "position": 2,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 24
                          },
                          "wait_after_previous": {
                            "value": 1,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        },
                        {
                          "id": "81573e28-456d-5a69-9c16-b7e2082b589a",
                          "key": "step-3",
                          "situation": null,
                          "position": 3,
                          "config_ref": {
                            "type": "custom",
                            "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          },
                          "kind": "message",
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 24
                          },
                          "wait_after_previous": {
                            "value": 1,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "step-1",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-1",
                          "to": "step-2",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-2",
                          "to": "step-3",
                          "kind": "next",
                          "situation": null
                        },
                        {
                          "from": "step-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": null
                        },
                        {
                          "from": "step-3",
                          "to": "end",
                          "kind": "end",
                          "situation": null
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": null,
                          "after_step": null,
                          "label": "Pasadas las 24 h, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end",
                          "situation": null,
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Termina",
                          "detail": null,
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": null,
                      "draft": {
                        "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                        "change_count": 6,
                        "updated_at": "2026-11-02T15:00:00.000Z"
                      }
                    },
                    "draft": {
                      "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "sequence_key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                      "based_on_version": null,
                      "change_count": 6,
                      "created_at": "2026-11-02T15:00:00.000Z",
                      "updated_at": "2026-11-02T15:00:00.000Z",
                      "created_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "updated_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222"
                      },
                      "definition": {
                        "schema_version": 1,
                        "name": "Seguimiento de cotización",
                        "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                        "entry": {
                          "type": "event",
                          "event": "quoted_not_booked",
                          "filters": []
                        },
                        "goal": {
                          "type": "books_visit"
                        },
                        "agent_id": null,
                        "priority": 0,
                        "situations": [],
                        "steps": [
                          {
                            "key": "step-1",
                            "situation": null,
                            "kind": "message",
                            "position": 1,
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 3,
                              "unit": "days",
                              "from": "start",
                              "from_step": null,
                              "hours": 72
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          },
                          {
                            "key": "step-2",
                            "situation": null,
                            "kind": "message",
                            "position": 2,
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-1",
                              "hours": 24
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          },
                          {
                            "key": "step-3",
                            "situation": null,
                            "kind": "message",
                            "position": 3,
                            "channel": "call",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-2",
                              "hours": 24
                            },
                            "mode": "approval",
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            },
                            "config_ref": null
                          }
                        ],
                        "connections": [
                          {
                            "from": "entry",
                            "to": "step-1",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "step-2",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "step-3",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "end",
                            "kind": "end",
                            "situation": null
                          }
                        ],
                        "ends": [
                          {
                            "key": "end",
                            "situation": null,
                            "type": "finish",
                            "after": null,
                            "continues_in": null
                          }
                        ],
                        "settings": {
                          "type": "none"
                        }
                      },
                      "sequence": {
                        "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "kind": "custom",
                        "name": "Seguimiento de cotización",
                        "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                        "status": "draft",
                        "entry": {
                          "type": "event",
                          "event": "quoted_not_booked",
                          "filters": []
                        },
                        "goal": {
                          "type": "books_visit",
                          "label": "Agenda visita"
                        },
                        "agent_id": null,
                        "collection": false,
                        "priority": 4,
                        "situations": [],
                        "steps": [
                          {
                            "id": "affce008-6fdb-52eb-8273-329b6da03c90",
                            "key": "step-1",
                            "situation": null,
                            "position": 1,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 3,
                              "unit": "days",
                              "from": "start",
                              "from_step": null,
                              "hours": 72
                            },
                            "wait_after_previous": {
                              "value": 3,
                              "unit": "days"
                            },
                            "at_hours": 72,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": true,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          },
                          {
                            "id": "fed60c11-4a4e-50dd-adbd-bdc25a39fc35",
                            "key": "step-2",
                            "situation": null,
                            "position": 2,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "whatsapp",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-1",
                              "hours": 24
                            },
                            "wait_after_previous": {
                              "value": 1,
                              "unit": "days"
                            },
                            "at_hours": 96,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": true,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          },
                          {
                            "id": "81573e28-456d-5a69-9c16-b7e2082b589a",
                            "key": "step-3",
                            "situation": null,
                            "position": 3,
                            "config_ref": {
                              "type": "custom",
                              "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f"
                            },
                            "warnings": [],
                            "stats": {
                              "waiting_now": 0,
                              "sent_30d": 0,
                              "reply_rate_30d": null,
                              "daily_14d": [
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0,
                                0
                              ]
                            },
                            "kind": "message",
                            "channel": "call",
                            "fallback_channel": null,
                            "wait": {
                              "type": "duration",
                              "value": 1,
                              "unit": "days",
                              "from": "previous_step",
                              "from_step": "step-2",
                              "hours": 24
                            },
                            "wait_after_previous": {
                              "value": 1,
                              "unit": "days"
                            },
                            "at_hours": 120,
                            "mode": "approval",
                            "mode_effective": "approval",
                            "sends": true,
                            "not_sent_reason": null,
                            "needs_template": false,
                            "content": {
                              "type": "ai_message",
                              "template_after_window": true
                            }
                          }
                        ],
                        "connections": [
                          {
                            "from": "entry",
                            "to": "step-1",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-1",
                            "to": "step-2",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-2",
                            "to": "step-3",
                            "kind": "next",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "exit",
                            "kind": "reply_exit",
                            "situation": null
                          },
                          {
                            "from": "step-3",
                            "to": "end",
                            "kind": "end",
                            "situation": null
                          }
                        ],
                        "window_markers": [
                          {
                            "situation": null,
                            "after_step": null,
                            "label": "Pasadas las 24 h, solo plantillas aprobadas"
                          }
                        ],
                        "ends": [
                          {
                            "key": "end",
                            "situation": null,
                            "type": "finish",
                            "after": null,
                            "at_hours": null,
                            "label": "Termina",
                            "detail": null,
                            "continues_in": null
                          }
                        ],
                        "stats": {
                          "waiting_now": 0,
                          "sent_30d": 0,
                          "reply_rate_30d": null,
                          "daily_14d": [
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0,
                            0
                          ]
                        },
                        "published_version": null,
                        "draft": {
                          "version_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e10",
                          "change_count": 6,
                          "updated_at": "2026-11-02T15:00:00.000Z"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/simulate": {
      "post": {
        "summary": "Simulate what one customer will receive",
        "description": "Walks «Si no responde» for ONE person: a real `contact_id` or a test `persona` (`has_whatsapp`, `has_email` optional, `opted_out`), from the moment our last message went out (`last_message_at`), with what the customer does (`behavior`: `never_replies`, `replies_after_step`; `pays` and `promises_date` are collections behaviors and are no-ops for sequences without payment steps). AI call steps end as `call_outcomes` says (`{step_id: outcome}`, default `no_answer`; nothing is called and no model runs). Returns the `timeline` (each step with the moment the ladder draws it, `planned_at`, and the moment the engine really sends it after contact hours and pacing, `shifted_at`, with `shift_reason`, a `preview` when a stored draft or approved template exists, and a `status`) and the real `end` reason: replied, end_of_sequence, marked_lost, no_fitting_approved_message, no_channel_left, message_limit_reached or opted_out. It is the same simulation `GET /followups/recontacto/plan` renders, so it follows the engine’s rules (contact hours from 9:00, the 24 h WhatsApp window, per-person limits, opt-out). Deterministic: no model call, nothing is sent or stored. `version` is `published` (default), `draft` (the open draft, `GET /sequences/{id}/draft`) or a version id (`GET /sequences/{id}/versions`). `builtin:si-no-responde` and custom sequences are simulable (a custom sequence walks its own steps through the same engine rules; steps timed before an appointment are listed in `notes`). Custom sequences with conditions, waits until and loops follow the scenario: `replies_after_step` → the `yes` branch of «¿Respondió?» (a reply ends the sequence only when its goal is a reply), `pays` → «¿Pagó?», `promises_date` → «Esperar hasta la fecha prometida» ends that day; `decisions` (`{step key or id: branch key}`) picks any condition’s branch, and «La IA decide» is never called here (without a decision it takes its first branch). The response then carries `path` (every step passed, the branch taken, loop laps) and timeline entries met again in a loop carry `iteration`. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequenceSimulateRequest"
              },
              "example": {
                "persona": {
                  "name": "Cliente de prueba",
                  "has_whatsapp": true,
                  "opted_out": false
                },
                "last_message_at": "2026-10-21T19:00:00Z",
                "situation": "never_replied",
                "behavior": {
                  "kind": "replies_after_step",
                  "step": 2
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The timeline and how it ends",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceSimulation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "timeline": [
                      {
                        "step_id": "c2f1a7b4-6d0e-5b8a-9a77-0b9f5d3e1c10",
                        "step_key": "never_replied-1",
                        "channel": "whatsapp",
                        "planned_at": "2026-10-21T22:00:00.000Z",
                        "shifted_at": "2026-10-21T22:00:00.000Z",
                        "status": "sends"
                      },
                      {
                        "step_id": "3a9d6e52-1f48-5c3b-8e0d-7a4c2b6f9d21",
                        "step_key": "never_replied-2",
                        "channel": "whatsapp",
                        "planned_at": "2026-10-22T03:00:00.000Z",
                        "shifted_at": "2026-10-22T12:00:00.000Z",
                        "shift_reason": "outside_contact_hours",
                        "status": "sends"
                      }
                    ],
                    "end": {
                      "reason": "replied",
                      "at_step": "3a9d6e52-1f48-5c3b-8e0d-7a4c2b6f9d21",
                      "at": "2026-10-22T12:00:00.000Z"
                    },
                    "notes": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/definition": {
      "get": {
        "summary": "Read a sequence’s whole definition",
        "description": "The complete `SequenceDefinition` of one version: the thing to start a draft from (`PUT /sequences/{id}/draft` takes the whole definition back). `version` is `published` (default; for a built-in, what its engine reads today, rungs and `settings` included), `draft`, or a version number. `version` and `version_id` are null for a built-in that was never versioned and for a draft. 404 `sequence_draft` when asking for a draft that does not exist, `sequence_version` for an unknown number. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "enum": [
                    "published"
                  ]
                },
                {
                  "type": "string",
                  "enum": [
                    "draft"
                  ]
                },
                {
                  "type": "integer",
                  "minimum": 1
                }
              ],
              "description": "`published` (default, what the engine reads today), `draft`, or a version number.",
              "example": "published"
            },
            "required": false,
            "name": "version",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The definition",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDefinitionView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "sequence_key": "builtin:si-no-responde",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "version": 2,
                    "status": "published",
                    "definition": {
                      "schema_version": 1,
                      "name": null,
                      "description": null,
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "steps": [
                        {
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "continues_in": null
                        }
                      ],
                      "situations": [
                        {
                          "key": "conversed",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "never_replied",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "lost",
                          "name": null,
                          "enabled": true
                        }
                      ],
                      "settings": {
                        "type": "recontacto",
                        "config": {
                          "enabled": true,
                          "daily_cap": 200,
                          "daily_review_budget": 500,
                          "lost_lookback_days": 90,
                          "max_evaluations_per_step": 3,
                          "re_evaluation_after_days": 1,
                          "excluded_template_ids": [],
                          "voice_examples": [],
                          "pacing": {
                            "min_gap_hours": null,
                            "max_per_day": null,
                            "pause_after_unanswered_days": null,
                            "approval_after_silence_days": null,
                            "approval_fallback_hours": null,
                            "entry_max_silence_days": 45,
                            "hot_leads_faster": false,
                            "max_paths_per_lead": 3,
                            "max_messages_per_person_90d": 6
                          },
                          "after_handoff": {
                            "followups": "draft_for_owner",
                            "auto_send_after_hours": 1,
                            "while_silent": "draft_auto_send",
                            "ai_answers_waiting": "after_minutes",
                            "ai_answers_waiting_after_minutes": 60,
                            "ai_answers_waiting_after_hours": 1,
                            "ai_answers_waiting_keep_owner": true
                          },
                          "situations": {
                            "never_replied": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "stalled": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "lost": {
                              "enabled": true,
                              "lose_after_days": null
                            }
                          },
                          "unanswered_message": {
                            "enabled": true,
                            "conversation_lifetime_cap": 4,
                            "template_after_window": true
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/draft": {
      "get": {
        "summary": "Read a sequence’s draft",
        "description": "Editing a sequence writes a DRAFT beside the published version; nothing reaches a customer until it is published. A draft holds the whole `SequenceDefinition`: the steps (channel, `wait`, `mode`), the situations (`enabled`), and for a built-in its `settings` (what is not a step: Recontacto’s «Ritmo», the first-touch source blocks). Publishing a built-in writes it through to the storage its engine reads (`/followups/recontacto`, the «Al llegar» source blocks, the `/followups` recipes) in the same transaction, so «Al llegar» is configurable here by API key and connector too. A published change to a wait applies to people already in the sequence from their next message.\n\nThe open draft with its definition and the sequence rendered from it (same shape as `GET /sequences/{id}`; the counts are today’s). 404 when there is no draft. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDraft"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "sequence_key": "builtin:si-no-responde",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "based_on_version": 1,
                    "change_count": 1,
                    "created_at": "2026-11-02T15:00:00.000Z",
                    "updated_at": "2026-11-02T15:04:00.000Z",
                    "created_by": {
                      "kind": "user",
                      "id": "22222222-2222-4222-8222-222222222222",
                      "via": null
                    },
                    "updated_by": {
                      "kind": "user",
                      "id": "22222222-2222-4222-8222-222222222222"
                    },
                    "definition": {
                      "schema_version": 1,
                      "name": null,
                      "description": null,
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "steps": [
                        {
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "continues_in": null
                        }
                      ],
                      "situations": [
                        {
                          "key": "conversed",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "never_replied",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "lost",
                          "name": null,
                          "enabled": true
                        }
                      ],
                      "settings": {
                        "type": "recontacto",
                        "config": {
                          "enabled": true,
                          "daily_cap": 200,
                          "daily_review_budget": 500,
                          "lost_lookback_days": 90,
                          "max_evaluations_per_step": 3,
                          "re_evaluation_after_days": 1,
                          "excluded_template_ids": [],
                          "voice_examples": [],
                          "pacing": {
                            "min_gap_hours": null,
                            "max_per_day": null,
                            "pause_after_unanswered_days": null,
                            "approval_after_silence_days": null,
                            "approval_fallback_hours": null,
                            "entry_max_silence_days": 45,
                            "hot_leads_faster": false,
                            "max_paths_per_lead": 3,
                            "max_messages_per_person_90d": 6
                          },
                          "after_handoff": {
                            "followups": "draft_for_owner",
                            "auto_send_after_hours": 1,
                            "while_silent": "draft_auto_send",
                            "ai_answers_waiting": "after_minutes",
                            "ai_answers_waiting_after_minutes": 60,
                            "ai_answers_waiting_after_hours": 1,
                            "ai_answers_waiting_keep_owner": true
                          },
                          "situations": {
                            "never_replied": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "stalled": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "lost": {
                              "enabled": true,
                              "lose_after_days": null
                            }
                          },
                          "unanswered_message": {
                            "enabled": true,
                            "conversation_lifetime_cap": 4,
                            "template_after_window": true
                          }
                        }
                      }
                    },
                    "sequence": {
                      "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                      "key": "builtin:si-no-responde",
                      "kind": "builtin",
                      "name": "Si no responde",
                      "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente vuelva a responder"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "situations": [
                        {
                          "key": "conversed",
                          "name": "Conversó y dejó de responder",
                          "enabled": true,
                          "first_step": "unanswered_message-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "never_replied",
                          "name": "Nunca respondió",
                          "enabled": true,
                          "first_step": "never_replied-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "lost",
                          "name": "Ya está perdido",
                          "enabled": true,
                          "first_step": "lost-1",
                          "waiting_now": 0,
                          "notes": [
                            "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                          ]
                        }
                      ],
                      "steps": [
                        {
                          "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "wait_after_previous": {
                            "value": 4,
                            "unit": "hours"
                          },
                          "at_hours": 4,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "hours"
                          },
                          "at_hours": 34,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [
                            "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 14,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 2,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "hours"
                          },
                          "at_hours": 3,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [
                            "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "hours"
                          },
                          "at_hours": 8,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "wait_after_previous": {
                            "value": 40,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "days"
                          },
                          "at_hours": 720,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": "conversed",
                          "after_step": "unanswered_message-1",
                          "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 264,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Fin de este camino.",
                          "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": 1,
                      "draft": {
                        "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                        "change_count": 1,
                        "updated_at": "2026-11-02T15:04:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Save a sequence’s draft",
        "description": "Editing a sequence writes a DRAFT beside the published version; nothing reaches a customer until it is published. A draft holds the whole `SequenceDefinition`: the steps (channel, `wait`, `mode`), the situations (`enabled`), and for a built-in its `settings` (what is not a step: Recontacto’s «Ritmo», the first-touch source blocks). Publishing a built-in writes it through to the storage its engine reads (`/followups/recontacto`, the «Al llegar» source blocks, the `/followups` recipes) in the same transaction, so «Al llegar» is configurable here by API key and connector too. A published change to a wait applies to people already in the sequence from their next message.\n\nCreates the draft or replaces it with `definition` (start from `GET /sequences/{id}/definition`). The stored draft is what the definition compiles to, read back: a built-in cannot express everything (a «Si no responde» step cannot be skipped, the first message of «Al llegar» always waits 5 minutes), and what it cannot express is a 400 naming the step. A built-in’s first draft also records today’s config as version 1. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "definition": {
                    "$ref": "#/components/schemas/SequenceDefinition"
                  }
                },
                "required": [
                  "definition"
                ],
                "additionalProperties": false
              },
              "example": {
                "definition": {
                  "schema_version": 1,
                  "name": null,
                  "description": null,
                  "entry": {
                    "type": "event",
                    "event": "customer_went_quiet",
                    "filters": [
                      {
                        "field": "situation",
                        "op": "in",
                        "values": [
                          "conversed",
                          "never_replied",
                          "lost"
                        ]
                      }
                    ]
                  },
                  "goal": {
                    "type": "reply"
                  },
                  "agent_id": null,
                  "priority": 3,
                  "steps": [
                    {
                      "key": "unanswered_message-1",
                      "situation": "conversed",
                      "kind": "message",
                      "position": 1,
                      "channel": "same_chat",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 4,
                        "unit": "hours",
                        "from": "start",
                        "from_step": null,
                        "hours": 4
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "unanswered_message",
                        "step": 1,
                        "after_hours": 4,
                        "unit": null,
                        "autonomy": "auto",
                        "channel": null,
                        "min_score": null
                      }
                    },
                    {
                      "key": "unanswered_message-2",
                      "situation": "conversed",
                      "kind": "message",
                      "position": 2,
                      "channel": "same_chat",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 30,
                        "unit": "hours",
                        "from": "previous_step",
                        "from_step": "unanswered_message-1",
                        "hours": 30
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "unanswered_message",
                        "step": 2,
                        "after_hours": 30,
                        "unit": null,
                        "autonomy": "auto",
                        "channel": null,
                        "min_score": null
                      }
                    },
                    {
                      "key": "stalled-1",
                      "situation": "conversed",
                      "kind": "message",
                      "position": 3,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 2,
                        "unit": "days",
                        "from": "start",
                        "from_step": null,
                        "hours": 48
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "stalled",
                        "step": 1,
                        "after_hours": 48,
                        "unit": "d",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "stalled-2",
                      "situation": "conversed",
                      "kind": "message",
                      "position": 4,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 2,
                        "unit": "days",
                        "from": "previous_step",
                        "from_step": "stalled-1",
                        "hours": 48
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "stalled",
                        "step": 2,
                        "after_hours": 48,
                        "unit": "d",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "never_replied-1",
                      "situation": "never_replied",
                      "kind": "message",
                      "position": 1,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 3,
                        "unit": "hours",
                        "from": "start",
                        "from_step": null,
                        "hours": 3
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "never_replied",
                        "step": 1,
                        "after_hours": 3,
                        "unit": "h",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "never_replied-2",
                      "situation": "never_replied",
                      "kind": "message",
                      "position": 2,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 5,
                        "unit": "hours",
                        "from": "previous_step",
                        "from_step": "never_replied-1",
                        "hours": 5
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "never_replied",
                        "step": 2,
                        "after_hours": 5,
                        "unit": "h",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "never_replied-3",
                      "situation": "never_replied",
                      "kind": "message",
                      "position": 3,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 40,
                        "unit": "hours",
                        "from": "previous_step",
                        "from_step": "never_replied-2",
                        "hours": 40
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "never_replied",
                        "step": 3,
                        "after_hours": 40,
                        "unit": "d",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "never_replied-4",
                      "situation": "never_replied",
                      "kind": "message",
                      "position": 4,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 3,
                        "unit": "days",
                        "from": "previous_step",
                        "from_step": "never_replied-3",
                        "hours": 72
                      },
                      "mode": "auto",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "never_replied",
                        "step": 4,
                        "after_hours": 72,
                        "unit": "d",
                        "autonomy": "auto",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    },
                    {
                      "key": "lost-1",
                      "situation": "lost",
                      "kind": "message",
                      "position": 1,
                      "channel": "whatsapp",
                      "fallback_channel": null,
                      "wait": {
                        "type": "duration",
                        "value": 30,
                        "unit": "days",
                        "from": "start",
                        "from_step": null,
                        "hours": 720
                      },
                      "mode": "approval",
                      "content": {
                        "type": "ai_message",
                        "template_after_window": true
                      },
                      "config_ref": {
                        "type": "recontacto",
                        "rail": "lost",
                        "step": 1,
                        "after_hours": 720,
                        "unit": "d",
                        "autonomy": "approval",
                        "channel": "whatsapp",
                        "min_score": null
                      }
                    }
                  ],
                  "connections": [
                    {
                      "from": "entry",
                      "to": "unanswered_message-1",
                      "kind": "next",
                      "situation": "conversed"
                    },
                    {
                      "from": "unanswered_message-1",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "conversed"
                    },
                    {
                      "from": "unanswered_message-1",
                      "to": "unanswered_message-2",
                      "kind": "next",
                      "situation": "conversed"
                    },
                    {
                      "from": "unanswered_message-2",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "conversed"
                    },
                    {
                      "from": "unanswered_message-2",
                      "to": "stalled-1",
                      "kind": "next",
                      "situation": "conversed"
                    },
                    {
                      "from": "stalled-1",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "conversed"
                    },
                    {
                      "from": "stalled-1",
                      "to": "stalled-2",
                      "kind": "next",
                      "situation": "conversed"
                    },
                    {
                      "from": "stalled-2",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "conversed"
                    },
                    {
                      "from": "stalled-2",
                      "to": "end:conversed",
                      "kind": "end",
                      "situation": "conversed"
                    },
                    {
                      "from": "entry",
                      "to": "never_replied-1",
                      "kind": "next",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-1",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-1",
                      "to": "never_replied-2",
                      "kind": "next",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-2",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-2",
                      "to": "never_replied-3",
                      "kind": "next",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-3",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-3",
                      "to": "never_replied-4",
                      "kind": "next",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-4",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "never_replied"
                    },
                    {
                      "from": "never_replied-4",
                      "to": "end:never_replied",
                      "kind": "end",
                      "situation": "never_replied"
                    },
                    {
                      "from": "entry",
                      "to": "lost-1",
                      "kind": "next",
                      "situation": "lost"
                    },
                    {
                      "from": "lost-1",
                      "to": "exit",
                      "kind": "reply_exit",
                      "situation": "lost"
                    },
                    {
                      "from": "lost-1",
                      "to": "end:lost",
                      "kind": "end",
                      "situation": "lost"
                    }
                  ],
                  "ends": [
                    {
                      "key": "end:conversed",
                      "situation": "conversed",
                      "type": "mark_lost",
                      "after": {
                        "value": 7,
                        "unit": "days"
                      },
                      "continues_in": {
                        "sequence_key": "builtin:si-no-responde",
                        "situation": "lost"
                      }
                    },
                    {
                      "key": "end:never_replied",
                      "situation": "never_replied",
                      "type": "mark_lost",
                      "after": {
                        "value": 7,
                        "unit": "days"
                      },
                      "continues_in": {
                        "sequence_key": "builtin:si-no-responde",
                        "situation": "lost"
                      }
                    },
                    {
                      "key": "end:lost",
                      "situation": "lost",
                      "type": "finish",
                      "after": null,
                      "continues_in": null
                    }
                  ],
                  "situations": [
                    {
                      "key": "conversed",
                      "name": null,
                      "enabled": true
                    },
                    {
                      "key": "never_replied",
                      "name": null,
                      "enabled": true
                    },
                    {
                      "key": "lost",
                      "name": null,
                      "enabled": true
                    }
                  ],
                  "settings": {
                    "type": "recontacto",
                    "config": {
                      "enabled": true,
                      "daily_cap": 200,
                      "daily_review_budget": 500,
                      "lost_lookback_days": 90,
                      "max_evaluations_per_step": 3,
                      "re_evaluation_after_days": 1,
                      "excluded_template_ids": [],
                      "voice_examples": [],
                      "pacing": {
                        "min_gap_hours": null,
                        "max_per_day": null,
                        "pause_after_unanswered_days": null,
                        "approval_after_silence_days": null,
                        "approval_fallback_hours": null,
                        "entry_max_silence_days": 45,
                        "hot_leads_faster": false,
                        "max_paths_per_lead": 3,
                        "max_messages_per_person_90d": 6
                      },
                      "after_handoff": {
                        "followups": "draft_for_owner",
                        "auto_send_after_hours": 1,
                        "while_silent": "draft_auto_send",
                        "ai_answers_waiting": "after_minutes",
                        "ai_answers_waiting_after_minutes": 60,
                        "ai_answers_waiting_after_hours": 1,
                        "ai_answers_waiting_keep_owner": true
                      },
                      "situations": {
                        "never_replied": {
                          "enabled": true,
                          "lose_after_days": 7
                        },
                        "stalled": {
                          "enabled": true,
                          "lose_after_days": 7
                        },
                        "lost": {
                          "enabled": true,
                          "lose_after_days": null
                        }
                      },
                      "unanswered_message": {
                        "enabled": true,
                        "conversation_lifetime_cap": 4,
                        "template_after_window": true
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDraft"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "sequence_key": "builtin:si-no-responde",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "based_on_version": 1,
                    "change_count": 1,
                    "created_at": "2026-11-02T15:00:00.000Z",
                    "updated_at": "2026-11-02T15:04:00.000Z",
                    "created_by": {
                      "kind": "user",
                      "id": "22222222-2222-4222-8222-222222222222",
                      "via": null
                    },
                    "updated_by": {
                      "kind": "user",
                      "id": "22222222-2222-4222-8222-222222222222"
                    },
                    "definition": {
                      "schema_version": 1,
                      "name": null,
                      "description": null,
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "steps": [
                        {
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "mode": "auto",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        },
                        {
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "continues_in": null
                        }
                      ],
                      "situations": [
                        {
                          "key": "conversed",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "never_replied",
                          "name": null,
                          "enabled": true
                        },
                        {
                          "key": "lost",
                          "name": null,
                          "enabled": true
                        }
                      ],
                      "settings": {
                        "type": "recontacto",
                        "config": {
                          "enabled": true,
                          "daily_cap": 200,
                          "daily_review_budget": 500,
                          "lost_lookback_days": 90,
                          "max_evaluations_per_step": 3,
                          "re_evaluation_after_days": 1,
                          "excluded_template_ids": [],
                          "voice_examples": [],
                          "pacing": {
                            "min_gap_hours": null,
                            "max_per_day": null,
                            "pause_after_unanswered_days": null,
                            "approval_after_silence_days": null,
                            "approval_fallback_hours": null,
                            "entry_max_silence_days": 45,
                            "hot_leads_faster": false,
                            "max_paths_per_lead": 3,
                            "max_messages_per_person_90d": 6
                          },
                          "after_handoff": {
                            "followups": "draft_for_owner",
                            "auto_send_after_hours": 1,
                            "while_silent": "draft_auto_send",
                            "ai_answers_waiting": "after_minutes",
                            "ai_answers_waiting_after_minutes": 60,
                            "ai_answers_waiting_after_hours": 1,
                            "ai_answers_waiting_keep_owner": true
                          },
                          "situations": {
                            "never_replied": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "stalled": {
                              "enabled": true,
                              "lose_after_days": 7
                            },
                            "lost": {
                              "enabled": true,
                              "lose_after_days": null
                            }
                          },
                          "unanswered_message": {
                            "enabled": true,
                            "conversation_lifetime_cap": 4,
                            "template_after_window": true
                          }
                        }
                      }
                    },
                    "sequence": {
                      "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                      "key": "builtin:si-no-responde",
                      "kind": "builtin",
                      "name": "Si no responde",
                      "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente vuelva a responder"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "situations": [
                        {
                          "key": "conversed",
                          "name": "Conversó y dejó de responder",
                          "enabled": true,
                          "first_step": "unanswered_message-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "never_replied",
                          "name": "Nunca respondió",
                          "enabled": true,
                          "first_step": "never_replied-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "lost",
                          "name": "Ya está perdido",
                          "enabled": true,
                          "first_step": "lost-1",
                          "waiting_now": 0,
                          "notes": [
                            "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                          ]
                        }
                      ],
                      "steps": [
                        {
                          "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "wait_after_previous": {
                            "value": 4,
                            "unit": "hours"
                          },
                          "at_hours": 4,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "hours"
                          },
                          "at_hours": 34,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [
                            "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 14,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 2,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "hours"
                          },
                          "at_hours": 3,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [
                            "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "hours"
                          },
                          "at_hours": 8,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "wait_after_previous": {
                            "value": 40,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "days"
                          },
                          "at_hours": 720,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": "conversed",
                          "after_step": "unanswered_message-1",
                          "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 264,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Fin de este camino.",
                          "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": 1,
                      "draft": {
                        "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                        "change_count": 1,
                        "updated_at": "2026-11-02T15:04:00.000Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard a sequence’s draft",
        "description": "Deletes the open draft («Descartar cambios»); the published version is untouched. Idempotent: `discarded: false` when there was none. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Whether a draft was discarded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "discarded": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "discarded"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "discarded": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/draft/diff": {
      "get": {
        "summary": "What publishing the draft would change",
        "description": "Every change of the draft against the published version: `field` (`steps.stalled-2.wait`, `situations.lost.enabled`, `settings.pacing.max_per_day`), `before`, `after`, and `people_affected_now`: the people at that step (or in that situation, or in the sequence) right now. For a built-in the published version is what its engine reads today. 404 when there is no draft. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The changes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDraftDiff"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "base_version": 1,
                    "change_count": 1,
                    "changes": [
                      {
                        "kind": "changed",
                        "field": "steps.stalled-2.wait",
                        "step_key": "stalled-2",
                        "situation": "conversed",
                        "before": {
                          "value": 3,
                          "unit": "days"
                        },
                        "after": {
                          "value": 2,
                          "unit": "days"
                        },
                        "people_affected_now": 12
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/draft/validation": {
      "get": {
        "summary": "What blocks publishing the draft",
        "description": "Checks the open draft the way publishing does. `blockers` stop `POST /sequences/{id}/publish`; `warnings` do not. Each problem has a machine-readable `code`, the `step_id` it is about (null for the whole draft), a Spanish `message` and a `fix`: `kind` is `draft_template` (have the AI write the missing WhatsApp template: `POST /followups/recontacto/template-draft`), `connect_channel`, `request_review`, `remove_step`, `change_channel`, `pick_agent` or `contact_support`, with a `target` (the same vocabulary as notifications) when there is a screen for it. Blocker codes: `no_fitting_approved_template` (a WhatsApp step past the 24 h window with no approved template Vitrina can fill), `channel_not_connected` (WhatsApp, email, Instagram), `step_type_unavailable` (a channel the workspace cannot use yet, e.g. SMS), `agent_unavailable` (the chosen AI agent is no longer active), `agent_not_supported` (an agent set on «Al llegar» or «Avisos»), `review_required`. Warning codes: `instagram_past_24h`, `whatsapp_past_24h_without_template`. Steps that never send (mode `skip`, a situation switched off) are not checked. Never calls a model. 404 when there is no draft. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The problems",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceDraftValidation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "blockers": [
                      {
                        "code": "no_fitting_approved_template",
                        "step_id": "stalled-2",
                        "message": "Este paso escribe pasadas las 24 horas y para eso necesita una plantilla de WhatsApp aprobada. Todavía no hay ninguna que sirva.",
                        "fix": {
                          "kind": "draft_template",
                          "target": {
                            "kind": "page",
                            "page": "settings.templates"
                          }
                        }
                      }
                    ],
                    "warnings": [
                      {
                        "code": "instagram_past_24h",
                        "step_id": "stalled-3",
                        "message": "Instagram solo deja escribir dentro de las 24 horas desde el último mensaje del cliente. Este paso no se enviará pasado ese plazo.",
                        "fix": {
                          "kind": "change_channel"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/collection-rules": {
      "get": {
        "summary": "Collection rules of the workspace",
        "description": "The days, hours and caps collection sequences obey on top of the shared guards (contact hours, per-person limits): `weekdays`, the `hours` window, `calls_per_day`, `calls_per_week`, `messages_per_week`, `min_spacing_hours` and `over_cap` (`skip` or `move`). The defaults are the limits of Ley 19.496 art. 37 (Ley 21.320) and CPC art. 59: Monday to Saturday except holidays, 08:00 to 20:00, one call per week, two messages per week at least 48 h apart. `ceilings` are those legal limits: a workspace may be stricter, never looser. `version` is 0 until the rules are saved. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "responses": {
          "200": {
            "description": "The rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CollectionRulesView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rules": {
                      "weekdays": [
                        1,
                        2,
                        3,
                        4,
                        5,
                        6
                      ],
                      "hours": {
                        "start": "08:00",
                        "end": "20:00"
                      },
                      "skip_holidays": true,
                      "calls_per_day": 1,
                      "calls_per_week": 1,
                      "messages_per_week": 2,
                      "min_spacing_hours": 48,
                      "over_cap": "skip"
                    },
                    "version": 3,
                    "is_default": false,
                    "updated_at": "2027-03-02T14:20:00.000Z",
                    "updated_by": {
                      "kind": "user",
                      "id": "0192f0b2-0000-7000-8000-00000000a001"
                    },
                    "ceilings": {
                      "weekdays": [
                        1,
                        2,
                        3,
                        4,
                        5,
                        6
                      ],
                      "hours": {
                        "start": "08:00",
                        "end": "20:00"
                      },
                      "calls_per_day": 1,
                      "calls_per_week": 1,
                      "messages_per_week": 2,
                      "min_spacing_hours": 48
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Replace the collection rules",
        "description": "Replaces the rules (fields left out take the platform defaults). 422 `VALIDATION_ERROR` with `details.problems` when a value is looser than the law allows. A real change raises `version`, which makes every recorded legal review outdated: collection sequences need a new review before they publish again. Saving the rules already in force changes nothing. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CollectionRulesReplaceRequest"
              },
              "example": {
                "messages_per_week": 1,
                "hours": {
                  "start": "09:00",
                  "end": "19:00"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CollectionRulesView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "rules": {
                      "weekdays": [
                        1,
                        2,
                        3,
                        4,
                        5,
                        6
                      ],
                      "hours": {
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "skip_holidays": true,
                      "calls_per_day": 1,
                      "calls_per_week": 1,
                      "messages_per_week": 1,
                      "min_spacing_hours": 48,
                      "over_cap": "skip"
                    },
                    "version": 3,
                    "is_default": false,
                    "updated_at": "2027-03-02T14:20:00.000Z",
                    "updated_by": {
                      "kind": "user",
                      "id": "0192f0b2-0000-7000-8000-00000000a001"
                    },
                    "ceilings": {
                      "weekdays": [
                        1,
                        2,
                        3,
                        4,
                        5,
                        6
                      ],
                      "hours": {
                        "start": "08:00",
                        "end": "20:00"
                      },
                      "calls_per_day": 1,
                      "calls_per_week": 1,
                      "messages_per_week": 2,
                      "min_spacing_hours": 48
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/legal-review": {
      "get": {
        "summary": "Where the legal review of a collection sequence stands",
        "description": "`status`: `not_required` (not a collection sequence), `missing`, `valid` (recorded against the current rules version) or `outdated` (the rules changed since). `review` is the newest recorded one. Publishing a collection sequence is refused with `review_required` unless the status is `valid`. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The review state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceLegalReview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "0192f0b2-0000-7000-8000-00000000c001",
                    "required": true,
                    "status": "valid",
                    "rules_version": 3,
                    "review": {
                      "id": "0192f0b2-0000-7000-8000-00000000d001",
                      "reviewer_member_id": "0192f0b2-0000-7000-8000-00000000a002",
                      "reviewed_on": "2027-03-02",
                      "note": "Revisado con la abogada del estudio",
                      "attachment_url": "https://docs.ejemplo.cl/opinion-legal.pdf",
                      "rules_version": 3,
                      "created_at": "2027-03-02T14:25:00.000Z",
                      "created_by": {
                        "kind": "user",
                        "id": "0192f0b2-0000-7000-8000-00000000a001"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record the legal review of a collection sequence",
        "description": "Records who reviewed the sequence and the rules (`reviewer_member_id`, `reviewed_on`, a `note` and/or an `attachment_url`) against the current rules version, which clears the `review_required` blocker. Append-only: the history is kept. 422 for a sequence that is not a collection sequence; 409 `rules_version_stale` when `rules_version` is no longer current. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequenceLegalReviewRequest"
              },
              "example": {
                "reviewer_member_id": "0192f0b2-0000-7000-8000-00000000a002",
                "reviewed_on": "2027-03-02",
                "note": "Revisado con la abogada del estudio",
                "attachment_url": "https://docs.ejemplo.cl/opinion-legal.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The review state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceLegalReview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "0192f0b2-0000-7000-8000-00000000c001",
                    "required": true,
                    "status": "valid",
                    "rules_version": 3,
                    "review": {
                      "id": "0192f0b2-0000-7000-8000-00000000d001",
                      "reviewer_member_id": "0192f0b2-0000-7000-8000-00000000a002",
                      "reviewed_on": "2027-03-02",
                      "note": "Revisado con la abogada del estudio",
                      "attachment_url": "https://docs.ejemplo.cl/opinion-legal.pdf",
                      "rules_version": 3,
                      "created_at": "2027-03-02T14:25:00.000Z",
                      "created_by": {
                        "kind": "user",
                        "id": "0192f0b2-0000-7000-8000-00000000a001"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/publish": {
      "post": {
        "summary": "Publish a sequence’s draft",
        "description": "The draft becomes the next numbered version, atomically: the previous version is archived and, for a built-in, its storage is rewritten in the same transaction. Recorded in the change history (who, when, the diff: `GET /sequences/{id}/versions`, and the audit log). 404 when there is no draft. 422 `VALIDATION_ERROR` while the draft has blockers: `details.problems` is the blocker list (same items as `GET /sequences/{id}/draft/validation`), `details.warnings` the rest. Requires `followups:write`. A publish that LOOSENS outbound (switching Recontacto or a first-touch source on, a step to `auto` or `auto_unless_assigned`, clearing or raising a limit) also needs `campaigns:write`, judged exactly as `PUT /followups/recontacto` judges the same edit; without it the answer is 403 and nothing changes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequencePublishResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "version": 2,
                    "published_at": "2026-11-02T15:06:00.000Z",
                    "origin": "edit",
                    "changes": [
                      {
                        "kind": "changed",
                        "field": "steps.stalled-2.wait",
                        "step_key": "stalled-2",
                        "situation": "conversed",
                        "before": {
                          "value": 3,
                          "unit": "days"
                        },
                        "after": {
                          "value": 2,
                          "unit": "days"
                        },
                        "people_affected_now": 12
                      }
                    ],
                    "sequence": {
                      "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                      "key": "builtin:si-no-responde",
                      "kind": "builtin",
                      "name": "Si no responde",
                      "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente vuelva a responder"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "situations": [
                        {
                          "key": "conversed",
                          "name": "Conversó y dejó de responder",
                          "enabled": true,
                          "first_step": "unanswered_message-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "never_replied",
                          "name": "Nunca respondió",
                          "enabled": true,
                          "first_step": "never_replied-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "lost",
                          "name": "Ya está perdido",
                          "enabled": true,
                          "first_step": "lost-1",
                          "waiting_now": 0,
                          "notes": [
                            "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                          ]
                        }
                      ],
                      "steps": [
                        {
                          "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "wait_after_previous": {
                            "value": 4,
                            "unit": "hours"
                          },
                          "at_hours": 4,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "hours"
                          },
                          "at_hours": 34,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [
                            "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 14,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 2,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "hours"
                          },
                          "at_hours": 3,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [
                            "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "hours"
                          },
                          "at_hours": 8,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "wait_after_previous": {
                            "value": 40,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "days"
                          },
                          "at_hours": 720,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": "conversed",
                          "after_step": "unanswered_message-1",
                          "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 264,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Fin de este camino.",
                          "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": 2,
                      "draft": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/versions": {
      "get": {
        "summary": "List a sequence’s versions",
        "description": "Every numbered version, newest first: `status` (`published` is the one running, `archived` the history), `origin` (`edit` a published draft, `revert`, `materialized` = version 1 read from the config a built-in had, `external` = that config was changed through its own route since), who created and published it, when, and the `changes` it published. Empty until the first draft. `limit` 1–200 (default 50). Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SequenceVersion"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                      "version": 2,
                      "status": "published",
                      "origin": "edit",
                      "based_on_version": 1,
                      "reverted_from": null,
                      "change_count": 1,
                      "changes": [
                        {
                          "kind": "changed",
                          "field": "steps.stalled-2.wait",
                          "step_key": "stalled-2",
                          "situation": "conversed",
                          "before": {
                            "value": 3,
                            "unit": "days"
                          },
                          "after": {
                            "value": 2,
                            "unit": "days"
                          },
                          "people_affected_now": 12
                        }
                      ],
                      "created_at": "2026-11-02T15:00:00.000Z",
                      "created_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "published_at": "2026-11-02T15:06:00.000Z",
                      "published_by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222"
                      }
                    },
                    {
                      "id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0f",
                      "version": 1,
                      "status": "archived",
                      "origin": "materialized",
                      "based_on_version": null,
                      "reverted_from": null,
                      "change_count": 0,
                      "changes": [],
                      "created_at": "2026-11-02T15:00:00.000Z",
                      "created_by": {
                        "kind": "system",
                        "id": null,
                        "via": null
                      },
                      "published_at": "2026-11-02T15:00:00.000Z",
                      "published_by": {
                        "kind": "system",
                        "id": null
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/versions/{version}/revert": {
      "post": {
        "summary": "Go back to a version",
        "description": "Publishes a NEW version equal to `version` (history is never rewritten; «deshacer» after a publish is a revert to the version before it). An open draft stays as it is. 404 for an unknown version. Requires `followups:write`. A publish that LOOSENS outbound (switching Recontacto or a first-touch source on, a step to `auto` or `auto_unless_assigned`, clearing or raising a limit) also needs `campaigns:write`, judged exactly as `PUT /followups/recontacto` judges the same edit; without it the answer is 403 and nothing changes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "required": true,
            "name": "version",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequencePublishResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sequence_id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                    "version_id": "0192f0a1-7c3e-7b2a-9d4e-5f6a7b8c9d0e",
                    "version": 3,
                    "published_at": "2026-11-02T15:06:00.000Z",
                    "origin": "revert",
                    "changes": [
                      {
                        "kind": "changed",
                        "field": "steps.stalled-2.wait",
                        "step_key": "stalled-2",
                        "situation": "conversed",
                        "before": {
                          "value": 2,
                          "unit": "days"
                        },
                        "after": {
                          "value": 3,
                          "unit": "days"
                        },
                        "people_affected_now": 12
                      }
                    ],
                    "sequence": {
                      "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                      "key": "builtin:si-no-responde",
                      "kind": "builtin",
                      "name": "Si no responde",
                      "description": "Le escribimos a quien dejó de responder, nunca respondió o ya está perdido.",
                      "status": "active",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": [
                          {
                            "field": "situation",
                            "op": "in",
                            "values": [
                              "conversed",
                              "never_replied",
                              "lost"
                            ]
                          }
                        ]
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Que el cliente vuelva a responder"
                      },
                      "agent_id": null,
                      "priority": 3,
                      "situations": [
                        {
                          "key": "conversed",
                          "name": "Conversó y dejó de responder",
                          "enabled": true,
                          "first_step": "unanswered_message-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "never_replied",
                          "name": "Nunca respondió",
                          "enabled": true,
                          "first_step": "never_replied-1",
                          "waiting_now": 0,
                          "notes": []
                        },
                        {
                          "key": "lost",
                          "name": "Ya está perdido",
                          "enabled": true,
                          "first_step": "lost-1",
                          "waiting_now": 0,
                          "notes": [
                            "A quien compró en otro lado o dijo que no quiere, no le escribimos."
                          ]
                        }
                      ],
                      "steps": [
                        {
                          "id": "8a385269-303e-5d82-8ce3-d2c82d1c2af7",
                          "key": "unanswered_message-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 1,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 4,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 4
                          },
                          "wait_after_previous": {
                            "value": 4,
                            "unit": "hours"
                          },
                          "at_hours": 4,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": false,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 1,
                            "after_hours": 4,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "4a6f17b4-0255-5c56-bdc5-93d14b544d5d",
                          "key": "unanswered_message-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 2,
                          "channel": "same_chat",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "unanswered_message-1",
                            "hours": 30
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "hours"
                          },
                          "at_hours": 34,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "unanswered_message",
                            "step": 2,
                            "after_hours": 30,
                            "unit": null,
                            "autonomy": "auto",
                            "channel": null,
                            "min_score": null
                          },
                          "warnings": [
                            "Instagram no permite escribir pasadas 24 h: este mensaje no saldrá a quien no tenga teléfono."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "7876340a-bc26-5e3c-8ef7-778cd18f1eda",
                          "key": "stalled-1",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 14,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 1,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "3f04a26b-1479-5a3b-9f58-95c52e16c0a5",
                          "key": "stalled-2",
                          "situation": "conversed",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "stalled-1",
                            "hours": 48
                          },
                          "wait_after_previous": {
                            "value": 2,
                            "unit": "days"
                          },
                          "at_hours": 96,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "stalled",
                            "step": 2,
                            "after_hours": 48,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "ec87b5be-92d7-51a5-9936-c5131838d9ab",
                          "key": "never_replied-1",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 3
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "hours"
                          },
                          "at_hours": 3,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 1,
                            "after_hours": 3,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [
                            "Muy pronto: un seguimiento a menos de 12 horas de nuestro primer mensaje casi nunca tiene respuesta."
                          ],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "9211cfce-9740-5f79-b76c-962cb908f160",
                          "key": "never_replied-2",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-1",
                            "hours": 5
                          },
                          "wait_after_previous": {
                            "value": 5,
                            "unit": "hours"
                          },
                          "at_hours": 8,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 2,
                            "after_hours": 5,
                            "unit": "h",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "2e4ea654-9d30-50e7-b586-6aae005a35f3",
                          "key": "never_replied-3",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 40,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "never_replied-2",
                            "hours": 40
                          },
                          "wait_after_previous": {
                            "value": 40,
                            "unit": "hours"
                          },
                          "at_hours": 48,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 3,
                            "after_hours": 40,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "5a157200-6687-519a-b3a1-7c7da36b88d8",
                          "key": "never_replied-4",
                          "situation": "never_replied",
                          "kind": "message",
                          "position": 4,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "never_replied-3",
                            "hours": 72
                          },
                          "wait_after_previous": {
                            "value": 3,
                            "unit": "days"
                          },
                          "at_hours": 120,
                          "mode": "auto",
                          "mode_effective": "auto",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "never_replied",
                            "step": 4,
                            "after_hours": 72,
                            "unit": "d",
                            "autonomy": "auto",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        },
                        {
                          "id": "09d4bf1d-8cbb-553c-8b68-ff0373a4cb37",
                          "key": "lost-1",
                          "situation": "lost",
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 30,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 720
                          },
                          "wait_after_previous": {
                            "value": 30,
                            "unit": "days"
                          },
                          "at_hours": 720,
                          "mode": "approval",
                          "mode_effective": "approval",
                          "sends": true,
                          "not_sent_reason": null,
                          "needs_template": true,
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": {
                            "type": "recontacto",
                            "rail": "lost",
                            "step": 1,
                            "after_hours": 720,
                            "unit": "d",
                            "autonomy": "approval",
                            "channel": "whatsapp",
                            "min_score": null
                          },
                          "warnings": [],
                          "stats": {
                            "waiting_now": 0,
                            "sent_30d": 0,
                            "reply_rate_30d": null,
                            "daily_14d": [
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0,
                              0
                            ]
                          }
                        }
                      ],
                      "connections": [
                        {
                          "from": "entry",
                          "to": "unanswered_message-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-1",
                          "to": "unanswered_message-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "unanswered_message-2",
                          "to": "stalled-1",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-1",
                          "to": "stalled-2",
                          "kind": "next",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "conversed"
                        },
                        {
                          "from": "stalled-2",
                          "to": "end:conversed",
                          "kind": "end",
                          "situation": "conversed"
                        },
                        {
                          "from": "entry",
                          "to": "never_replied-1",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-1",
                          "to": "never_replied-2",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-2",
                          "to": "never_replied-3",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-3",
                          "to": "never_replied-4",
                          "kind": "next",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "never_replied"
                        },
                        {
                          "from": "never_replied-4",
                          "to": "end:never_replied",
                          "kind": "end",
                          "situation": "never_replied"
                        },
                        {
                          "from": "entry",
                          "to": "lost-1",
                          "kind": "next",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "exit",
                          "kind": "reply_exit",
                          "situation": "lost"
                        },
                        {
                          "from": "lost-1",
                          "to": "end:lost",
                          "kind": "end",
                          "situation": "lost"
                        }
                      ],
                      "window_markers": [
                        {
                          "situation": "conversed",
                          "after_step": "unanswered_message-1",
                          "label": "se cierra la ventana de WhatsApp (se cuenta desde el último mensaje del cliente): desde aquí, solo plantillas aprobadas"
                        }
                      ],
                      "ends": [
                        {
                          "key": "end:conversed",
                          "situation": "conversed",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 264,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:never_replied",
                          "situation": "never_replied",
                          "type": "mark_lost",
                          "after": {
                            "value": 7,
                            "unit": "days"
                          },
                          "at_hours": 288,
                          "label": "Lo marcamos perdido",
                          "detail": "Si sigue sin responder 7 días después del último mensaje.",
                          "continues_in": {
                            "sequence_key": "builtin:si-no-responde",
                            "situation": "lost"
                          }
                        },
                        {
                          "key": "end:lost",
                          "situation": "lost",
                          "type": "finish",
                          "after": null,
                          "at_hours": null,
                          "label": "Fin de este camino.",
                          "detail": "Si vuelve a escribir y después se queda en silencio, empieza otro.",
                          "continues_in": null
                        }
                      ],
                      "stats": {
                        "waiting_now": 0,
                        "sent_30d": 0,
                        "reply_rate_30d": null,
                        "daily_14d": [
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0,
                          0
                        ]
                      },
                      "published_version": 3,
                      "draft": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/templates": {
      "get": {
        "summary": "List the templates for a new sequence",
        "description": "A custom sequence is the workspace’s own: who enters (`entry`: an event — `lead_arrived`, `customer_went_quiet`, `stage_changed`, `quoted_not_booked`, `appointment_booked`; `signed_up_not_bought` and `installment_overdue` are accepted but cannot be published yet; or a `list`, enrolled with consent through `POST /sequences/{id}/enrollments/from-list` — with filters on `interest`, `source`, `stage` or `tag`), the `goal` that makes a person leave when met (`reply`, `books_visit`, `books_demo`; `pays` cannot be published yet) and the steps, edited as a draft and run once published (`POST /sequences/{id}/publish`). A person is in ONE sequence at a time among «Si no responde» and the custom ones, and enters the highest-priority one they qualify for; «Al llegar» and «Avisos» stay outside that rule. The engine sends through the same guards as «Si no responde»: contact hours, per-person limits, opt-out, the 24 h WhatsApp window, pauses and «Con tu OK».\n\nThe «Nueva secuencia» catalogue: Seguimiento de cotización, Reactivar clientes, Se registró y no pagó, Cobranza de cuotas, Recordatorio de cita and Desde cero, each with its entry, goal and suggested steps (a step whose channel is not available yet is flagged by the draft validation, not hidden). Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "es",
                "en"
              ]
            },
            "required": false,
            "name": "lang",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SequenceTemplate"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "key": "quote_followup",
                      "name": "Seguimiento de cotización",
                      "description": "Cotizó y no agendó visita: recordatorio, luego un llamado.",
                      "entry": {
                        "type": "event",
                        "event": "quoted_not_booked",
                        "filters": []
                      },
                      "goal": {
                        "type": "books_visit",
                        "label": "Agenda visita"
                      },
                      "channels": [
                        "whatsapp",
                        "whatsapp",
                        "call"
                      ],
                      "steps": [
                        {
                          "key": "step-1",
                          "situation": null,
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 3,
                            "unit": "days",
                            "from": "start",
                            "from_step": null,
                            "hours": 72
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Recordatorio de la cotización"
                        },
                        {
                          "key": "step-2",
                          "situation": null,
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Quedan 24 h de cotización"
                        },
                        {
                          "key": "step-3",
                          "situation": null,
                          "kind": "message",
                          "position": 3,
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Llamada para agendar"
                        }
                      ]
                    },
                    {
                      "key": "reactivate",
                      "name": "Reactivar clientes",
                      "description": "Clientes con una compra anterior: novedad y oferta.",
                      "entry": {
                        "type": "list",
                        "list_id": null
                      },
                      "goal": {
                        "type": "books_visit",
                        "label": "Agenda visita"
                      },
                      "channels": [
                        "whatsapp",
                        "email",
                        "whatsapp"
                      ],
                      "steps": [
                        {
                          "key": "step-1",
                          "situation": null,
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 0,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 0
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Novedad personalizada"
                        },
                        {
                          "key": "step-2",
                          "situation": null,
                          "kind": "message",
                          "position": 2,
                          "channel": "email",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 2,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 48
                          },
                          "mode": "approval",
                          "content": {
                            "type": "email",
                            "subject": null,
                            "custom_body": false
                          },
                          "config_ref": null,
                          "label": "Detalle de la oferta"
                        },
                        {
                          "key": "step-3",
                          "situation": null,
                          "kind": "message",
                          "position": 3,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 5,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 120
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Último recordatorio"
                        }
                      ]
                    },
                    {
                      "key": "signup_not_paid",
                      "name": "Se registró y no pagó",
                      "description": "Usuarios que crearon cuenta pero no compraron: ofrece una demo.",
                      "entry": {
                        "type": "list",
                        "list_id": null
                      },
                      "goal": {
                        "type": "books_demo",
                        "label": "Agenda demo"
                      },
                      "channels": [
                        "email",
                        "whatsapp",
                        "call",
                        "email"
                      ],
                      "steps": [
                        {
                          "key": "step-1",
                          "situation": null,
                          "kind": "message",
                          "position": 1,
                          "channel": "email",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 0,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 0
                          },
                          "mode": "approval",
                          "content": {
                            "type": "email",
                            "subject": null,
                            "custom_body": false
                          },
                          "config_ref": null,
                          "label": "Bienvenida + link a demo"
                        },
                        {
                          "key": "step-2",
                          "situation": null,
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Te ayudo a configurarlo"
                        },
                        {
                          "key": "step-3",
                          "situation": null,
                          "kind": "message",
                          "position": 3,
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Llamada para agendar demo"
                        },
                        {
                          "key": "step-4",
                          "situation": null,
                          "kind": "message",
                          "position": 4,
                          "channel": "email",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 32,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "step-3",
                            "hours": 32
                          },
                          "mode": "approval",
                          "content": {
                            "type": "email",
                            "subject": null,
                            "custom_body": false
                          },
                          "config_ref": null,
                          "label": "Caso de éxito de su rubro"
                        }
                      ]
                    },
                    {
                      "key": "installments",
                      "name": "Cobranza de cuotas",
                      "description": "Cuota vencida: correo, llamada, WhatsApp y aviso formal.",
                      "entry": {
                        "type": "event",
                        "event": "installment_overdue",
                        "filters": []
                      },
                      "goal": {
                        "type": "pays",
                        "label": "Paga"
                      },
                      "channels": [
                        "email",
                        "call",
                        "whatsapp",
                        "call",
                        "email"
                      ],
                      "steps": [
                        {
                          "key": "step-1",
                          "situation": null,
                          "kind": "message",
                          "position": 1,
                          "channel": "email",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 0,
                            "unit": "hours",
                            "from": "start",
                            "from_step": null,
                            "hours": 0
                          },
                          "mode": "approval",
                          "content": {
                            "type": "email",
                            "subject": null,
                            "custom_body": false
                          },
                          "config_ref": null,
                          "label": "Recordatorio con link de pago"
                        },
                        {
                          "key": "step-2",
                          "situation": null,
                          "kind": "message",
                          "position": 2,
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-1",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Llamada de cobranza"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "call-1-outcome",
                          "kind": "condition",
                          "position": 3,
                          "wait": {
                            "type": "duration",
                            "value": 0,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 0
                          },
                          "test": "call_outcome",
                          "ai_prompt": null,
                          "branches": [
                            {
                              "key": "promised_pay",
                              "label": "Prometió pagar",
                              "to": "promise-1-wait"
                            },
                            {
                              "key": "no_answer",
                              "label": "No contestó",
                              "to": "step-3"
                            }
                          ],
                          "default_branch": "no_answer",
                          "label": "¿Cómo salió la llamada?"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "promise-1-wait",
                          "kind": "wait_until",
                          "position": 4,
                          "until": {
                            "kind": "date_field",
                            "field": "promised_date"
                          },
                          "timeout": {
                            "value": 14,
                            "unit": "days"
                          },
                          "label": "Esperar hasta la fecha prometida"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "paid-1",
                          "kind": "condition",
                          "position": 5,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "promise-1-wait",
                            "hours": 24
                          },
                          "test": "paid",
                          "ai_prompt": null,
                          "branches": [
                            {
                              "key": "yes",
                              "label": "Sí",
                              "to": "end"
                            },
                            {
                              "key": "no",
                              "label": "No",
                              "to": "step-3"
                            }
                          ],
                          "default_branch": null,
                          "label": "¿Pagó?"
                        },
                        {
                          "key": "step-3",
                          "situation": null,
                          "kind": "message",
                          "position": 6,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-2",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Recordatorio + link"
                        },
                        {
                          "key": "step-4",
                          "situation": null,
                          "kind": "message",
                          "position": 7,
                          "channel": "call",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "step-3",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Segunda llamada"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "call-2-outcome",
                          "kind": "condition",
                          "position": 8,
                          "wait": {
                            "type": "duration",
                            "value": 0,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "step-4",
                            "hours": 0
                          },
                          "test": "call_outcome",
                          "ai_prompt": null,
                          "branches": [
                            {
                              "key": "promised_pay",
                              "label": "Prometió pagar",
                              "to": "promise-2-wait"
                            },
                            {
                              "key": "no_answer",
                              "label": "No contestó",
                              "to": "step-5"
                            }
                          ],
                          "default_branch": "no_answer",
                          "label": "¿Cómo salió la llamada?"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "promise-2-wait",
                          "kind": "wait_until",
                          "position": 9,
                          "until": {
                            "kind": "date_field",
                            "field": "promised_date"
                          },
                          "timeout": {
                            "value": 14,
                            "unit": "days"
                          },
                          "label": "Esperar hasta la fecha prometida"
                        },
                        {
                          "situation": null,
                          "config_ref": null,
                          "key": "paid-2",
                          "kind": "condition",
                          "position": 10,
                          "wait": {
                            "type": "duration",
                            "value": 1,
                            "unit": "days",
                            "from": "previous_step",
                            "from_step": "promise-2-wait",
                            "hours": 24
                          },
                          "test": "paid",
                          "ai_prompt": null,
                          "branches": [
                            {
                              "key": "yes",
                              "label": "Sí",
                              "to": "end"
                            },
                            {
                              "key": "no",
                              "label": "No",
                              "to": "step-5"
                            }
                          ],
                          "default_branch": null,
                          "label": "¿Pagó?"
                        },
                        {
                          "key": "step-5",
                          "situation": null,
                          "kind": "message",
                          "position": 11,
                          "channel": "email",
                          "fallback_channel": null,
                          "wait": {
                            "type": "duration",
                            "value": 32,
                            "unit": "hours",
                            "from": "previous_step",
                            "from_step": "step-4",
                            "hours": 32
                          },
                          "mode": "approval",
                          "content": {
                            "type": "email",
                            "subject": null,
                            "custom_body": false
                          },
                          "config_ref": null,
                          "label": "Aviso formal"
                        }
                      ]
                    },
                    {
                      "key": "appointment_reminder",
                      "name": "Recordatorio de cita",
                      "description": "Antes de una visita o cita agendada, confirma asistencia.",
                      "entry": {
                        "type": "event",
                        "event": "appointment_booked",
                        "filters": []
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Responde"
                      },
                      "channels": [
                        "whatsapp",
                        "whatsapp"
                      ],
                      "steps": [
                        {
                          "key": "step-1",
                          "situation": null,
                          "kind": "message",
                          "position": 1,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "before_appointment",
                            "value": 24,
                            "unit": "hours",
                            "hours": 24
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Confirmar asistencia"
                        },
                        {
                          "key": "step-2",
                          "situation": null,
                          "kind": "message",
                          "position": 2,
                          "channel": "whatsapp",
                          "fallback_channel": null,
                          "wait": {
                            "type": "before_appointment",
                            "value": 2,
                            "unit": "hours",
                            "hours": 2
                          },
                          "mode": "approval",
                          "content": {
                            "type": "ai_message",
                            "template_after_window": true
                          },
                          "config_ref": null,
                          "label": "Te esperamos hoy"
                        }
                      ]
                    },
                    {
                      "key": "blank",
                      "name": "Desde cero",
                      "description": "Elige quién entra, el objetivo y arma los pasos.",
                      "entry": {
                        "type": "event",
                        "event": "customer_went_quiet",
                        "filters": []
                      },
                      "goal": {
                        "type": "reply",
                        "label": "Responde"
                      },
                      "channels": [],
                      "steps": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/order": {
      "put": {
        "summary": "Set the priority order of the sequences",
        "description": "The ONE priority list a person is placed by: `builtin:si-no-responde` and every custom sequence id, highest first, each exactly once (400 otherwise, naming what is missing or unknown). A person who qualifies for two enters only the higher one, and is in one at a time. «Avisos» (1) and «Al llegar» (2) keep their places outside the list. Answers the list with each sequence’s `id`, `key` and `priority` as `GET /sequences` shows it (a built-in’s uuid is accepted too). Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequenceOrderRequest"
              },
              "example": {
                "order": [
                  "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                  "builtin:si-no-responde"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "order": [
                      {
                        "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "key": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                        "priority": 3
                      },
                      {
                        "id": "dd63825c-cb16-553d-90cc-6cb4fa694ce8",
                        "key": "builtin:si-no-responde",
                        "priority": 4
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/{enrollment_id}/promised-date": {
      "post": {
        "summary": "Record the date a customer promised",
        "description": "Sets (or clears, with `date: null`) the day a person in a custom sequence promised, e.g. to pay. A step `wait_until` `{kind:\"date_field\", field:\"promised_date\"}` («Esperar hasta la fecha prometida») waits until 00:00 of that day in the workspace’s time zone instead of its timeout, and consumes the date when it ends (a loop back waits for a NEW promise). A person already waiting at such a step is re-timed at once (`waiting_for_date: true`). The `enrollment_id` travels on every message the sequence queues (`decision_payload.enrollment_id` on `GET /outbound/approvals`). 404 `sequence_enrollment` when the person is no longer in that sequence. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "enrollment_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequencePromisedDateRequest"
              },
              "example": {
                "date": "2026-11-15"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The enrollment and its promised date",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollmentPromise"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enrollment_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e11",
                    "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                    "contact_id": "33333333-3333-4333-8333-333333333333",
                    "promised_date": "2026-11-15",
                    "current_step": "step-2",
                    "next_due_at": "2026-11-02T15:00:00.000Z",
                    "waiting_for_date": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/from-list": {
      "post": {
        "summary": "Preview and record the consent to enroll a list",
        "description": "Step 1 of enrolling a list into a custom sequence. Records the consent basis (`customer_relationship`, `explicit_opt_in`, `other_documented`) with the evidence `note`, who chose it and when, and answers the funnel: `total` (the list) -> `reachable` (a usable phone or email for the first step, not opted out) -> `will_enroll` (free to enter), with a count per exclusion reason (`blocked`, `spam`, `no_identifier`, `opted_out` by reason, `in_higher_sequence`, ...). For WhatsApp the `warnings` carry the cost note: the first message is an approved marketing template and Meta charges per marketing conversation. **Nothing is sent and nobody is enrolled**: the batch is `pending_activation` until `POST .../activate`. A list is a saved audience; importing contacts never enrolls anyone. 201. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SequenceEnrollFromListRequest"
              },
              "example": {
                "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                "consent_basis": "explicit_opt_in",
                "note": "Aceptaron términos al registrarse"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The pending batch with its funnel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollmentBatch"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                    "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                    "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                    "list_name": "Registrados sin comprar",
                    "status": "pending_activation",
                    "channel": "whatsapp",
                    "consent": {
                      "basis": "explicit_opt_in",
                      "note": "Aceptaron términos al registrarse",
                      "by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "at": "2026-11-02T15:00:00.000Z"
                    },
                    "funnel": {
                      "total": 120,
                      "reachable": 96,
                      "will_enroll": 90,
                      "excluded": {
                        "blocked": 2,
                        "spam": 0,
                        "archived": 1,
                        "no_identifier": 15,
                        "invalid_identifier": 3,
                        "duplicate": 3,
                        "opted_out": 5,
                        "already_in_sequence": 0,
                        "in_higher_sequence": 6,
                        "in_lower_sequence": 0,
                        "in_recontacto": 0
                      },
                      "opted_out_by": {
                        "stop_keyword": 4,
                        "unsubscribe": 1
                      }
                    },
                    "warnings": [
                      {
                        "code": "whatsapp_marketing_cost",
                        "message": "El primer mensaje por WhatsApp a quien no te escribió en las últimas 24 horas sale como plantilla de marketing aprobada, y Meta cobra cada conversación de marketing. Revisa la tarifa vigente en tu cuenta de Meta antes de activar."
                      }
                    ],
                    "result": null,
                    "activated_at": null,
                    "activated_by": null,
                    "created_at": "2026-11-02T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/batches": {
      "get": {
        "summary": "List the enrollment batches of a sequence",
        "description": "Every batch made from a list for this sequence, newest first, with status, consent and funnel. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The batches",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SequenceEnrollmentBatch"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                      "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                      "list_name": "Registrados sin comprar",
                      "status": "pending_activation",
                      "channel": "whatsapp",
                      "consent": {
                        "basis": "explicit_opt_in",
                        "note": "Aceptaron términos al registrarse",
                        "by": {
                          "kind": "user",
                          "id": "22222222-2222-4222-8222-222222222222",
                          "via": null
                        },
                        "at": "2026-11-02T15:00:00.000Z"
                      },
                      "funnel": {
                        "total": 120,
                        "reachable": 96,
                        "will_enroll": 90,
                        "excluded": {
                          "blocked": 2,
                          "spam": 0,
                          "archived": 1,
                          "no_identifier": 15,
                          "invalid_identifier": 3,
                          "duplicate": 3,
                          "opted_out": 5,
                          "already_in_sequence": 0,
                          "in_higher_sequence": 6,
                          "in_lower_sequence": 0,
                          "in_recontacto": 0
                        },
                        "opted_out_by": {
                          "stop_keyword": 4,
                          "unsubscribe": 1
                        }
                      },
                      "warnings": [
                        {
                          "code": "whatsapp_marketing_cost",
                          "message": "El primer mensaje por WhatsApp a quien no te escribió en las últimas 24 horas sale como plantilla de marketing aprobada, y Meta cobra cada conversación de marketing. Revisa la tarifa vigente en tu cuenta de Meta antes de activar."
                        }
                      ],
                      "result": null,
                      "activated_at": null,
                      "activated_by": null,
                      "created_at": "2026-11-02T15:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/batches/{batch_id}": {
      "get": {
        "summary": "Read an enrollment batch",
        "description": "The batch status (`pending_activation`, `activated`, `cancelled`), its consent, the preview funnel and, once activated, the `result` (`enrolled` and what was skipped and why). Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "batch_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollmentBatch"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                    "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                    "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                    "list_name": "Registrados sin comprar",
                    "status": "pending_activation",
                    "channel": "whatsapp",
                    "consent": {
                      "basis": "explicit_opt_in",
                      "note": "Aceptaron términos al registrarse",
                      "by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "at": "2026-11-02T15:00:00.000Z"
                    },
                    "funnel": {
                      "total": 120,
                      "reachable": 96,
                      "will_enroll": 90,
                      "excluded": {
                        "blocked": 2,
                        "spam": 0,
                        "archived": 1,
                        "no_identifier": 15,
                        "invalid_identifier": 3,
                        "duplicate": 3,
                        "opted_out": 5,
                        "already_in_sequence": 0,
                        "in_higher_sequence": 6,
                        "in_lower_sequence": 0,
                        "in_recontacto": 0
                      },
                      "opted_out_by": {
                        "stop_keyword": 4,
                        "unsubscribe": 1
                      }
                    },
                    "warnings": [
                      {
                        "code": "whatsapp_marketing_cost",
                        "message": "El primer mensaje por WhatsApp a quien no te escribió en las últimas 24 horas sale como plantilla de marketing aprobada, y Meta cobra cada conversación de marketing. Revisa la tarifa vigente en tu cuenta de Meta antes de activar."
                      }
                    ],
                    "result": null,
                    "activated_at": null,
                    "activated_by": null,
                    "created_at": "2026-11-02T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/batches/{batch_id}/activate": {
      "post": {
        "summary": "Activate an enrollment batch",
        "description": "Step 2: the owner starts it. Everyone is checked again (opt-out, blocked, no channel, exclusivity): a person already in a higher sequence (or in «Si no responde» when it ranks above) is skipped and counted in `result.excluded`. The rest are enrolled with the batch and basis on their enrollment, and the sequence runs them like any other: its own agent writes, contact hours, pause and per-person limits apply. The sequence must be published (409 `sequence_not_published`); a batch activates once (409 `batch_not_pending`). Requires `followups:write` and `campaigns:write` (it starts outbound messages).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "batch_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The activated batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollmentBatch"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                    "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                    "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                    "list_name": "Registrados sin comprar",
                    "status": "activated",
                    "channel": "whatsapp",
                    "consent": {
                      "basis": "explicit_opt_in",
                      "note": "Aceptaron términos al registrarse",
                      "by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "at": "2026-11-02T15:00:00.000Z"
                    },
                    "funnel": {
                      "total": 120,
                      "reachable": 96,
                      "will_enroll": 90,
                      "excluded": {
                        "blocked": 2,
                        "spam": 0,
                        "archived": 1,
                        "no_identifier": 15,
                        "invalid_identifier": 3,
                        "duplicate": 3,
                        "opted_out": 5,
                        "already_in_sequence": 0,
                        "in_higher_sequence": 6,
                        "in_lower_sequence": 0,
                        "in_recontacto": 0
                      },
                      "opted_out_by": {
                        "stop_keyword": 4,
                        "unsubscribe": 1
                      }
                    },
                    "warnings": [
                      {
                        "code": "whatsapp_marketing_cost",
                        "message": "El primer mensaje por WhatsApp a quien no te escribió en las últimas 24 horas sale como plantilla de marketing aprobada, y Meta cobra cada conversación de marketing. Revisa la tarifa vigente en tu cuenta de Meta antes de activar."
                      }
                    ],
                    "result": {
                      "total": 120,
                      "reachable": 96,
                      "will_enroll": 90,
                      "excluded": {
                        "blocked": 2,
                        "spam": 0,
                        "archived": 1,
                        "no_identifier": 15,
                        "invalid_identifier": 3,
                        "duplicate": 3,
                        "opted_out": 5,
                        "already_in_sequence": 0,
                        "in_higher_sequence": 6,
                        "in_lower_sequence": 0,
                        "in_recontacto": 0
                      },
                      "opted_out_by": {
                        "stop_keyword": 4,
                        "unsubscribe": 1
                      },
                      "enrolled": 90
                    },
                    "activated_at": "2026-11-02T15:10:00.000Z",
                    "activated_by": {
                      "kind": "user",
                      "id": "22222222-2222-4222-8222-222222222222"
                    },
                    "created_at": "2026-11-02T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments/batches/{batch_id}/cancel": {
      "post": {
        "summary": "Cancel a pending enrollment batch",
        "description": "Drops a batch that was never activated; nothing was sent. 409 `batch_not_pending` otherwise. Requires `followups:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "batch_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled batch",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SequenceEnrollmentBatch"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                    "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                    "list_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e30",
                    "list_name": "Registrados sin comprar",
                    "status": "cancelled",
                    "channel": "whatsapp",
                    "consent": {
                      "basis": "explicit_opt_in",
                      "note": "Aceptaron términos al registrarse",
                      "by": {
                        "kind": "user",
                        "id": "22222222-2222-4222-8222-222222222222",
                        "via": null
                      },
                      "at": "2026-11-02T15:00:00.000Z"
                    },
                    "funnel": {
                      "total": 120,
                      "reachable": 96,
                      "will_enroll": 90,
                      "excluded": {
                        "blocked": 2,
                        "spam": 0,
                        "archived": 1,
                        "no_identifier": 15,
                        "invalid_identifier": 3,
                        "duplicate": 3,
                        "opted_out": 5,
                        "already_in_sequence": 0,
                        "in_higher_sequence": 6,
                        "in_lower_sequence": 0,
                        "in_recontacto": 0
                      },
                      "opted_out_by": {
                        "stop_keyword": 4,
                        "unsubscribe": 1
                      }
                    },
                    "warnings": [
                      {
                        "code": "whatsapp_marketing_cost",
                        "message": "El primer mensaje por WhatsApp a quien no te escribió en las últimas 24 horas sale como plantilla de marketing aprobada, y Meta cobra cada conversación de marketing. Revisa la tarifa vigente en tu cuenta de Meta antes de activar."
                      }
                    ],
                    "result": null,
                    "activated_at": null,
                    "activated_by": null,
                    "created_at": "2026-11-02T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sequences/{id}/enrollments": {
      "get": {
        "summary": "List the people in a sequence with their consent",
        "description": "Enrollments of the sequence, newest first, each with the consent it entered on (`consent.basis`, note, time; null for people who entered by an event). Filter by `contact_id` (a person’s record) or `batch_id`. Requires `followups:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "batch_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The enrollments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SequenceEnrollment"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e50",
                      "sequence_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e0f",
                      "contact_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e40",
                      "status": "active",
                      "exit_reason": null,
                      "entered_at": "2026-11-02T15:10:00.000Z",
                      "exited_at": null,
                      "batch_id": "0192f0b2-1a2b-7c3d-8e4f-5a6b7c8d9e20",
                      "consent": {
                        "basis": "explicit_opt_in",
                        "note": "Aceptaron términos al registrarse",
                        "at": "2026-11-02T15:00:00.000Z"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers": {
      "get": {
        "summary": "List the tenant’s help centers",
        "description": "Every help center (portal) in the workspace, with its publish state and locales.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "responses": {
          "200": {
            "description": "Help centers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "slug": "ayuda",
                      "name": "Centro de ayuda",
                      "brand": null,
                      "logo_url": null,
                      "primary_color": null,
                      "default_locale": "es",
                      "supported_locales": [
                        "es",
                        "en"
                      ],
                      "custom_subdomain": null,
                      "is_published": true,
                      "settings": {},
                      "created_at": "2026-08-20T12:00:00.000Z",
                      "updated_at": "2026-09-10T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a help center",
        "description": "A new help center starts unpublished — set `is_published` via PUT once its content is ready. `slug` is unique per tenant; a collision is a 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "brand": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "logo_url": {
                    "type": "string",
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "primary_color": {
                    "type": "string",
                    "pattern": "^#[0-9A-Fa-f]{6}$"
                  },
                  "default_locale": {
                    "type": "string",
                    "pattern": "^[a-z]{2}(-[A-Z]{2})?$",
                    "default": "en"
                  },
                  "supported_locales": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                    },
                    "maxItems": 40,
                    "default": []
                  },
                  "custom_subdomain": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
                  }
                },
                "required": [
                  "slug",
                  "name"
                ]
              },
              "example": {
                "name": "Centro de ayuda",
                "slug": "ayuda"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created help center",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "slug": "ayuda",
                    "name": "Centro de ayuda",
                    "brand": null,
                    "logo_url": null,
                    "primary_color": null,
                    "default_locale": "es",
                    "supported_locales": [
                      "es",
                      "en"
                    ],
                    "custom_subdomain": null,
                    "is_published": false,
                    "settings": {},
                    "created_at": "2026-08-20T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/help-centers/articles/all": {
      "get": {
        "summary": "List articles across every help center in the tenant",
        "description": "Tenant-wide article list, not scoped to one portal — drives the admin “all articles” view. Unlike the per-help-center list, this route does not validate `status`: an unknown value simply matches nothing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "scheduled",
                "archived"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Articles across the tenant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b2b2b2b2-0000-4000-8000-000000000001",
                      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                      "slug": "garantia-de-fabrica",
                      "status": "published",
                      "position": 0,
                      "scheduled_publish_at": null,
                      "translations": [
                        {
                          "locale": "es",
                          "title": "Garantía de fábrica",
                          "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                        }
                      ],
                      "created_at": "2026-08-21T12:00:00.000Z",
                      "updated_at": "2026-09-10T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}": {
      "get": {
        "summary": "Get one help center",
        "description": "The full portal row: branding, locales configuration, publish state.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The help center",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "slug": "ayuda",
                    "name": "Centro de ayuda",
                    "brand": null,
                    "logo_url": null,
                    "primary_color": null,
                    "default_locale": "es",
                    "supported_locales": [
                      "es",
                      "en"
                    ],
                    "custom_subdomain": null,
                    "is_published": true,
                    "settings": {},
                    "created_at": "2026-08-20T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a help center",
        "description": "Partial update — at least one field is required. Nullable fields (`brand`, `logo_url`, `primary_color`, `custom_subdomain`) are cleared by sending `null`; omitting them leaves them untouched. `is_published` is what makes the portal reachable on the public reader.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "logo_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "primary_color": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^#[0-9A-Fa-f]{6}$"
                  },
                  "default_locale": {
                    "type": "string",
                    "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                  },
                  "supported_locales": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                    },
                    "maxItems": 40
                  },
                  "custom_subdomain": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$"
                  },
                  "is_published": {
                    "type": "boolean"
                  },
                  "settings": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                }
              },
              "example": {
                "is_published": true,
                "primary_color": "#0EA5E9"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated help center",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "slug": "ayuda",
                    "name": "Centro de ayuda",
                    "brand": null,
                    "logo_url": null,
                    "primary_color": "#0EA5E9",
                    "default_locale": "es",
                    "supported_locales": [
                      "es",
                      "en"
                    ],
                    "custom_subdomain": null,
                    "is_published": true,
                    "settings": {},
                    "created_at": "2026-08-20T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a help center",
        "description": "Removes the portal and everything under it — sections, articles and translations.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/sections": {
      "get": {
        "summary": "List the section tree",
        "description": "Flat list; nesting is expressed by `parent_id`, ordering by `position`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Sections",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a9a9a9a9-0000-4000-8000-000000000001",
                      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "slug": "garantia",
                      "parent_id": null,
                      "position": 0,
                      "created_at": "2026-08-20T12:10:00.000Z",
                      "updated_at": "2026-08-20T12:10:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a section",
        "description": "A section carries no text of its own — the title and description live in translations, so one is required at create time to name it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "parent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "translation": {
                    "type": "object",
                    "properties": {
                      "locale": {
                        "type": "string",
                        "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                      },
                      "title": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "description": {
                        "type": "string",
                        "maxLength": 4000
                      }
                    },
                    "required": [
                      "locale",
                      "title"
                    ]
                  }
                },
                "required": [
                  "slug",
                  "translation"
                ]
              },
              "example": {
                "slug": "garantia",
                "translation": {
                  "locale": "es",
                  "title": "Garantía",
                  "description": "Todo sobre la garantía de fábrica"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created section",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "slug": "garantia",
                    "parent_id": null,
                    "position": 0,
                    "created_at": "2026-08-20T12:10:00.000Z",
                    "updated_at": "2026-08-20T12:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/sections/reorder": {
      "post": {
        "summary": "Reorder sections",
        "description": "Atomic: every `section_id` must belong to this help center, and one that does not fails the whole call rather than reordering the rest.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "order": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "section_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0,
                          "maximum": 1000000
                        }
                      },
                      "required": [
                        "section_id",
                        "position"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 1000
                  }
                },
                "required": [
                  "order"
                ]
              },
              "example": {
                "section_ids": [
                  "a9a9a9a9-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sections in their new order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a9a9a9a9-0000-4000-8000-000000000001",
                      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "slug": "garantia",
                      "parent_id": null,
                      "position": 0,
                      "created_at": "2026-08-20T12:10:00.000Z",
                      "updated_at": "2026-08-20T12:10:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/sections/{sectionId}": {
      "put": {
        "summary": "Update a section’s slug, parent or position",
        "description": "Structure only — title and description are per-locale, see the translation routes. A section cannot be reparented under itself.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "sectionId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "parent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  }
                }
              },
              "example": {
                "position": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated section",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "slug": "garantia",
                    "parent_id": null,
                    "position": 1,
                    "created_at": "2026-08-20T12:10:00.000Z",
                    "updated_at": "2026-08-20T12:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a section",
        "description": "Removes the section and its translations. Articles that were filed under it are not deleted — they lose their section (`section_id` becomes null).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "sectionId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/sections/{sectionId}/translations/{locale}": {
      "put": {
        "summary": "Create or replace a section translation",
        "description": "Upsert — the same call creates the locale or overwrites it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "sectionId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
            },
            "required": true,
            "name": "locale",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 4000
                  }
                },
                "required": [
                  "title"
                ]
              },
              "example": {
                "title": "Garantía",
                "description": "Todo sobre la garantía de fábrica"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted translation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "locale": "es",
                    "title": "Garantía",
                    "description": "Todo sobre la garantía de fábrica"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a section translation",
        "description": "Removes this ONE locale. The section itself, and its other locales, are untouched.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "sectionId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
            },
            "required": true,
            "name": "locale",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/articles": {
      "get": {
        "summary": "List articles in a help center",
        "description": "Articles in this ONE portal — see `GET /help-centers/articles/all` for the tenant-wide list. Filter by `section_id` or `status`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "section_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "published",
                "scheduled",
                "archived"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Articles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "b2b2b2b2-0000-4000-8000-000000000001",
                      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                      "slug": "garantia-de-fabrica",
                      "status": "published",
                      "position": 0,
                      "scheduled_publish_at": null,
                      "translations": [
                        {
                          "locale": "es",
                          "title": "Garantía de fábrica",
                          "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                        }
                      ],
                      "created_at": "2026-08-21T12:00:00.000Z",
                      "updated_at": "2026-09-10T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an article",
        "description": "An empty `translation.title` is allowed on purpose: the editor opens the new article immediately and the title is typed inline. New articles are always created `internal` — publish them with the publish route.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "section_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "translation": {
                    "type": "object",
                    "properties": {
                      "locale": {
                        "type": "string",
                        "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 240
                      },
                      "body_markdown": {
                        "type": "string",
                        "maxLength": 200000
                      },
                      "excerpt": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 1000
                      }
                    },
                    "required": [
                      "locale",
                      "title"
                    ]
                  }
                },
                "required": [
                  "slug",
                  "translation"
                ]
              },
              "example": {
                "translation": {
                  "locale": "es",
                  "title": ""
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "internal",
                    "position": 0,
                    "scheduled_publish_at": null,
                    "translations": [
                      {
                        "locale": "es",
                        "title": "",
                        "body": ""
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/from-file": {
      "post": {
        "summary": "Create an article from an uploaded document",
        "description": "Extracts the text of a PDF / DOCX / XLSX / plain-text upload server-side and stores it as one article — no object-storage round trip. The slug and title come from the filename. Rejects with 400 when fewer than 20 characters of usable text come out, which is the usual outcome for a scanned image PDF.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension. The JSON body is capped at 25 MB.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "politica-de-cambios.md",
                "content": "# Política de cambios\n\nTienes 30 días para cambiar tu compra.",
                "content_encoding": "utf8"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "internal",
                    "position": 0,
                    "scheduled_publish_at": null,
                    "translations": [
                      {
                        "locale": "es",
                        "title": "",
                        "body": ""
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/bulk-translate": {
      "post": {
        "summary": "Queue AI translation of every article into target locales",
        "description": "Asynchronous: enqueues one job per (article, locale) cell that needs work and returns immediately — the translations appear over the following minutes. `min_status` picks the cells: `missing` only fills gaps, `outdated` (the default) also re-translates cells whose source has changed since.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "target_locales": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
                    },
                    "minItems": 1,
                    "maxItems": 40
                  },
                  "min_status": {
                    "type": "string",
                    "enum": [
                      "missing",
                      "outdated"
                    ]
                  }
                },
                "required": [
                  "target_locales"
                ]
              },
              "example": {
                "target_locales": [
                  "en"
                ],
                "min_status": "outdated"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Translation jobs enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enqueued": {
                      "type": "integer",
                      "description": "Number of (article, locale) jobs queued"
                    },
                    "target_locales": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "articles": {
                      "type": "integer",
                      "description": "Number of articles considered"
                    }
                  },
                  "required": [
                    "enqueued",
                    "target_locales",
                    "articles"
                  ]
                },
                "example": {
                  "enqueued": 4,
                  "target_locales": [
                    "en"
                  ],
                  "articles": 4
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/help-centers/{id}/articles/{articleId}": {
      "get": {
        "summary": "Get one article with its translations",
        "description": "The full article row, every locale’s translation included.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "b2b2b2b2-0000-4000-8000-000000000001",
                  "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                  "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                  "slug": "garantia-de-fabrica",
                  "status": "published",
                  "position": 0,
                  "scheduled_publish_at": null,
                  "translations": [
                    {
                      "locale": "es",
                      "title": "Garantía de fábrica",
                      "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                    }
                  ],
                  "created_at": "2026-08-21T12:00:00.000Z",
                  "updated_at": "2026-09-10T12:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update an article’s placement or status",
        "description": "Structure and lifecycle only — the prose lives in translations. Prefer the publish / archive / schedule routes over setting `status` here: they run the side effects (KB sync on publish, scheduling on schedule) that a bare status write does not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9](?:[a-z0-9-]{0,118}[a-z0-9])?$"
                  },
                  "section_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "published",
                      "scheduled",
                      "archived"
                    ]
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "internal"
                    ]
                  },
                  "scheduled_publish_at": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "example": {
                "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                "position": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "published",
                    "position": 1,
                    "scheduled_publish_at": null,
                    "translations": [
                      {
                        "locale": "es",
                        "title": "Garantía de fábrica",
                        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an article",
        "description": "Removes the article and every locale’s translation. A published article is un-published and its KB chunks are removed first, so the agent stops quoting it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/articles/{articleId}/publish": {
      "post": {
        "summary": "Publish an article",
        "description": "Makes it visible on the public reader AND syncs its translations into the knowledge base, so the AI agent starts answering from it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "published",
                    "position": 0,
                    "scheduled_publish_at": null,
                    "translations": [
                      {
                        "locale": "es",
                        "title": "Garantía de fábrica",
                        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/archive": {
      "post": {
        "summary": "Archive an article",
        "description": "Withdraws it from the reader without deleting it; can be published again later.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The archived article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "archived",
                    "position": 0,
                    "scheduled_publish_at": null,
                    "translations": [
                      {
                        "locale": "es",
                        "title": "Garantía de fábrica",
                        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/schedule": {
      "post": {
        "summary": "Schedule an article to publish later",
        "description": "Moves it to `scheduled`; a background worker publishes it at `scheduled_publish_at`, running the same KB sync as a manual publish.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scheduled_publish_at": {
                    "type": "string",
                    "minLength": 1
                  }
                },
                "required": [
                  "scheduled_publish_at"
                ]
              },
              "example": {
                "scheduled_publish_at": "2026-09-25T13:00:00.000Z"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scheduled article",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b2b2b2b2-0000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "section_id": "a9a9a9a9-0000-4000-8000-000000000001",
                    "slug": "garantia-de-fabrica",
                    "status": "scheduled",
                    "position": 0,
                    "scheduled_publish_at": "2026-09-25T13:00:00.000Z",
                    "translations": [
                      {
                        "locale": "es",
                        "title": "Garantía de fábrica",
                        "body": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía."
                      }
                    ],
                    "created_at": "2026-08-21T12:00:00.000Z",
                    "updated_at": "2026-09-10T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/lock": {
      "post": {
        "summary": "Take the editor lock on an article",
        "description": "Advisory lock so two editors do not overwrite each other. **Requires a Supabase user session** — an `sk_*` API key has no user behind it and gets 400, since a lock has to be held by someone. The response reports who holds it, so losing the race is a 200, not an error.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lock state, including the current holder",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locked": true,
                    "locked_by": "11111111-0000-4000-8000-000000000001",
                    "locked_at": "2026-09-21T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Release the editor lock",
        "description": "Only clears the lock if the caller holds it. Requires a Supabase user session for the same reason as taking it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/feedback": {
      "get": {
        "summary": "Summarise reader feedback for one article",
        "description": "Aggregate of the helpful / not-helpful votes left by readers on the public portal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Feedback summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "helpful": 12,
                    "not_helpful": 1,
                    "total": 13
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/translations/{locale}": {
      "put": {
        "summary": "Create or replace an article translation",
        "description": "Upsert, and the route the editor autosaves through — which is why an empty `title` is accepted: it bounces 0-length drafts while the author is still typing. If the article is published, saving re-syncs the KB.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
            },
            "required": true,
            "name": "locale",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "maxLength": 240
                  },
                  "body_markdown": {
                    "type": "string",
                    "maxLength": 200000
                  },
                  "excerpt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "meta_description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 320
                  },
                  "seo_title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 240
                  },
                  "seo_image": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024
                  }
                },
                "required": [
                  "title",
                  "body_markdown"
                ]
              },
              "example": {
                "title": "Factory warranty",
                "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The upserted translation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "en",
                    "title": "Factory warranty",
                    "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an article translation",
        "description": "Removes this ONE locale’s translation. If the article is published and this was the last translation, the article-level KB entry for that locale is removed too.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
            },
            "required": true,
            "name": "locale",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/articles/{articleId}/translations/{locale}/generate": {
      "post": {
        "summary": "AI-translate one article into one locale",
        "description": "Synchronous single-cell counterpart to bulk-translate — it returns the finished translation rather than a job count, so it blocks for as long as the model takes. Overwrites any existing translation for that locale.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
            },
            "required": true,
            "name": "locale",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The generated translation",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "en",
                    "title": "Factory warranty",
                    "body": "Every new vehicle carries 3 years or 100,000 km of factory warranty.",
                    "generated": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/help-centers/{id}/articles/{articleId}/preview-link": {
      "post": {
        "summary": "Mint a signed preview token for an unpublished article",
        "description": "Lets a reviewer open a draft on the public reader without publishing it. The token is valid for 24 hours. `locale` may be sent in the body or as a `?locale=` query parameter; neither is a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "locale": {
                    "type": "string",
                    "minLength": 1,
                    "description": "May also be supplied as a `?locale=` query parameter."
                  }
                },
                "required": [
                  "locale"
                ]
              },
              "example": {
                "locale": "es"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ token, locale, expires_in }` — seconds until expiry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "token": "ptok_9f1c2e7a8b3d4e5f",
                    "locale": "es",
                    "expires_in": 86400
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/articles/{articleId}/resync-kb": {
      "post": {
        "summary": "Force a fresh KB chunk + embed pass on a published article",
        "description": "Publishing already syncs to the KB, so this is the escape hatch for when that sync was lost — after a backfill, an embedding-model change, or an edit made while the background sync was failing. Only published articles can be synced; anything else is a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "articleId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Per-locale sync result",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "synced_locales": [
                      "es",
                      "en"
                    ],
                    "chunks": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/feedback": {
      "get": {
        "summary": "List reader feedback across the help center",
        "description": "Portal-wide feed of reader votes and comments — the per-article view is nested under the article.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Feedback entries",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "article_id": "b2b2b2b2-0000-4000-8000-000000000001",
                      "locale": "es",
                      "helpful": true,
                      "comment": null,
                      "created_at": "2026-09-20T15:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/locales": {
      "get": {
        "summary": "List the locales a help center publishes in",
        "description": "Every locale this portal publishes, with which one is `is_default`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Locales",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "locale": "es",
                      "is_default": true
                    },
                    {
                      "locale": "en",
                      "is_default": false
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a locale to a help center",
        "description": "Answers **200, not 201** — an oddity of this route, kept for the clients already relying on it. Adding a locale does not translate anything; it opens the column that bulk-translate then fills.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "locale": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 16
                  },
                  "display_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "is_default": {
                    "type": "boolean"
                  },
                  "rtl": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "locale"
                ]
              },
              "example": {
                "locale": "en"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The added locale",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "en",
                    "is_default": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/locales/{locale}/default": {
      "post": {
        "summary": "Make a locale the default",
        "description": "The default locale is what a reader gets with no locale preference, and the source language bulk-translate translates *from*.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 16
            },
            "required": true,
            "name": "locale",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The new default locale",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locale": "en",
                    "is_default": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/locales/{locale}": {
      "delete": {
        "summary": "Remove a locale from a help center",
        "description": "Drops the locale and every translation filed under it (sections and articles). The default locale cannot be removed — reassign it first.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 16
            },
            "required": true,
            "name": "locale",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/help-centers/{id}/translation-coverage": {
      "get": {
        "summary": "Translation coverage matrix",
        "description": "Article × non-default-locale grid, each cell `up_to_date | outdated | missing | draft`, plus an overall percentage. This is the same computation bulk-translate uses to pick cells, so it doubles as a preview of what a bulk run would do.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Coverage matrix",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "locales": [
                      "en"
                    ],
                    "overall_pct": 75,
                    "articles": [
                      {
                        "article_id": "b2b2b2b2-0000-4000-8000-000000000001",
                        "slug": "garantia-de-fabrica",
                        "en": "up_to_date"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/media": {
      "get": {
        "summary": "List uploaded media",
        "description": "Every media asset uploaded to this portal’s article editor.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Media assets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a8a8a8a8-4000-4000-8000-000000000001",
                      "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                      "url": "https://cdn.vitrinadev.com/help-centers/ayuda/garantia.png",
                      "content_type": "image/png",
                      "size_bytes": 48213,
                      "created_at": "2026-09-05T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a media asset (multipart/form-data)",
        "description": "Accepts images, video and documents for the article editor’s media blocks, up to 50 MB. Host anything larger externally and link to it.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension. The JSON body is capped at 25 MB.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "tienda.png",
                "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The uploaded asset",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a8a8a8a8-4000-4000-8000-000000000001",
                    "help_center_id": "a8a8a8a8-0000-4000-8000-000000000001",
                    "url": "https://cdn.vitrinadev.com/help-centers/ayuda/garantia.png",
                    "content_type": "image/png",
                    "size_bytes": 48213,
                    "created_at": "2026-09-05T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/help-centers/{id}/media/{mediaId}": {
      "delete": {
        "summary": "Delete a media asset",
        "description": "Articles that still embed the asset are not rewritten — their image will 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Help Centers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "mediaId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/insights/resultados": {
      "get": {
        "summary": "What the AI did for the agenda (any vertical)",
        "description": "The same facts as `GET /clinic/insights/resultados`, for any vertical, with the vertical's vocabulary (`family`: a dealer reads visits and test drives, `clientes`). Blocks with no data for the vertical come back `applies: false` (money, confirmation, reminders for a dealer) rather than as zeros. Aggregates only. Requires `analytics:read`; CLP figures additionally require `clinic_money:read`. Accepts `from` / `to` (bare `YYYY-MM-DD`, `to` inclusive; default the 90 days ending today) and `location_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Insights"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`."
            },
            "required": false,
            "description": "First day of the window (by the day the cita happens), inclusive. Defaults to 90 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone."
            },
            "required": false,
            "description": "Last day of the window, INCLUSIVE. Defaults to today in the clinic's zone.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "One sucursal. Omit for all of them."
            },
            "required": false,
            "description": "One sucursal. Omit for all of them.",
            "name": "location_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tu día con Vitrina",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AgendaResults"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "from": "2026-07-01",
                    "to": "2026-09-28",
                    "timezone": "America/Santiago",
                    "location_id": null,
                    "family": "clinic",
                    "previous_period": {
                      "from": "2026-04-02",
                      "to": "2026-06-30"
                    },
                    "bookings": {
                      "ai": 286,
                      "agenda_total": 2011,
                      "share_pct": 14.2,
                      "previous": {
                        "ai": 240,
                        "agenda_total": 1950,
                        "share_pct": 12.3
                      },
                      "rule": "Citas del período (por la fecha de la cita) que agendó el asistente de Vitrina en una conversación, ÷ todas las citas del período."
                    },
                    "money": {
                      "applies": true,
                      "withheld": false,
                      "booked_clp": 11400000,
                      "booked_priced": 271,
                      "booked_unpriced": 15,
                      "agenda_clp": 96000000,
                      "clp_share_pct": 11.9,
                      "paid_clp": 7250000,
                      "paid_patients": 164,
                      "unlinked": 4,
                      "regime": "mirror",
                      "previous": {
                        "booked_clp": 9800000,
                        "paid_clp": 6100000
                      },
                      "rule_booked": "Valor de las citas que agendó Vitrina: el precio de sus prestaciones, o el del catálogo. Es lo agendado, no lo cobrado.",
                      "rule_paid": "Pagos recibidos en el período por los pacientes que Vitrina agendó, desde el día en que los agendó."
                    },
                    "out_of_hours": {
                      "configured": true,
                      "count": 111,
                      "pct": 38.8,
                      "previous_pct": 36.1,
                      "heatmap": [
                        {
                          "weekday": 1,
                          "hour": 21,
                          "count": 9
                        },
                        {
                          "weekday": 6,
                          "hour": 11,
                          "count": 14
                        }
                      ],
                      "rule": "Citas que Vitrina agendó mientras tu negocio estaba cerrado, según tu horario de atención (incluye feriados)."
                    },
                    "autonomy": {
                      "no_human": 158,
                      "pct": 56.6,
                      "measured": 279,
                      "unknown": 7,
                      "previous_pct": 52,
                      "rule": "Citas de Vitrina en las que nadie de tu equipo escribió en la conversación antes de agendar."
                    },
                    "time_to_book": {
                      "median_seconds": 734,
                      "p95_seconds": 80100,
                      "p95_phrase": "5 de cada 100 personas tardan más de 22 h 15 min en quedar agendadas.",
                      "measured": 279,
                      "unknown": 7,
                      "previous_median_seconds": 910,
                      "rule": "Tiempo desde el primer mensaje del paciente hasta que su cita quedó agendada (la mediana)."
                    },
                    "hours_returned": {
                      "minutes_per_booking": 10,
                      "hours": 47.7,
                      "rule": "Cada cita que agenda Vitrina ahorra 10 minutos de responder y agendar a mano."
                    },
                    "recovery": {
                      "window_days": 30,
                      "known_attendance": 1063,
                      "unknown_attendance": 21,
                      "no_show_pct": 10.2,
                      "no_show": {
                        "count": 108,
                        "recovered": 37,
                        "recovered_via_vitrina": 17,
                        "recovered_pct": 34.6,
                        "via_vitrina_pct": 15.9,
                        "pending": 12,
                        "unlinked": 1
                      },
                      "cancelled": {
                        "count": 240,
                        "recovered": 26,
                        "recovered_via_vitrina": 11,
                        "recovered_pct": 10.8,
                        "via_vitrina_pct": 4.6,
                        "pending": 18,
                        "unlinked": 0
                      },
                      "rule_no_show": "Citas pasadas marcadas como «no asistió» ÷ citas con asistencia conocida (asistió + no asistió).",
                      "rule_recovered": "Una cita perdida se recupera cuando el mismo paciente agenda otra dentro de 30 días."
                    },
                    "confirmation": {
                      "applies": true,
                      "confirmed_attendance_pct": 93.1,
                      "unconfirmed_attendance_pct": 78.4,
                      "confirmed_closed": 610,
                      "unconfirmed_closed": 212,
                      "rule": "Asistencia de las citas cuyo paciente confirmó el recordatorio, frente a las que nunca respondieron."
                    },
                    "ads": {
                      "state": "active",
                      "spend_clp": 1648000,
                      "booked": 58,
                      "attended": 41,
                      "paid": 22,
                      "cost_per_booked": 28414,
                      "cost_per_attended": 40195,
                      "cost_per_paid": 74909,
                      "rule": "Inversión en anuncios del período ÷ citas agendadas, asistidas y pacientes de anuncios que pagaron, según Vitrina Ads para las mismas fechas."
                    },
                    "reminders": {
                      "applies": true,
                      "sent": 721,
                      "failed": 120,
                      "failed_pct": 14.3,
                      "by_cause": [
                        {
                          "cause": "no_whatsapp",
                          "count": 61
                        },
                        {
                          "cause": "no_phone",
                          "count": 38
                        },
                        {
                          "cause": "meta_payment_block",
                          "count": 21
                        }
                      ],
                      "rule": "Recordatorios de citas del período que no salieron, agrupados por la causa que registró el envío."
                    },
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/intent-mix": {
      "get": {
        "summary": "Who wrote, by what they came for (sell / trade-in / buy)",
        "description": "For a business that buys AND sells: the people (distinct contacts) whose first message or lead fell in the window, split by what they came for — `sell` (sell us theirs), `trade_in` (give theirs and take another), `buy` — with the trend against the previous window of equal length, the funnel per intent (reached the board’s quote step, its visit step, won), and the split by entry: Meta campaign and ad set (the ad that opened the conversation, or a website form’s first-touch UTMs), the website, or the organic channel. Campaign spend and cost per person come from Vitrina Ads when the workspace has it (`spend_state`). A person with both a sell and a buy lead in the window counts as `trade_in`. `applies: false` when the workspace has no sell side (fewer than 10 people asking to sell or trade in over the last 90 days). Counts only. Requires `analytics:read`. Takes the insights `?window=` vocabulary.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The intent mix",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "applies": {
                          "type": "boolean",
                          "description": "False when the workspace had fewer than 10 people asking to sell or trade in over the last 90 days — a screen draws nothing then."
                        },
                        "previous_window": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string"
                            },
                            "to": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "from",
                            "to"
                          ],
                          "description": "The window of equal length right before, for the trend."
                        },
                        "totals": {
                          "type": "object",
                          "properties": {
                            "people": {
                              "type": "integer"
                            },
                            "previous_people": {
                              "type": "integer"
                            },
                            "delta_pct": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "people",
                            "previous_people",
                            "delta_pct"
                          ]
                        },
                        "intents": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "intent": {
                                "type": "string",
                                "enum": [
                                  "sell",
                                  "trade_in",
                                  "buy",
                                  "unknown"
                                ],
                                "description": "`sell` they sell us theirs · `trade_in` they give theirs and take another · `buy` they buy · `unknown` no lead said why."
                              },
                              "people": {
                                "type": "integer"
                              },
                              "share_pct": {
                                "type": "number",
                                "description": "Share of the window’s people, one decimal."
                              },
                              "previous_people": {
                                "type": "integer"
                              },
                              "delta_pct": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "% change vs the previous window; `null` when it had nobody."
                              },
                              "funnel": {
                                "type": "object",
                                "properties": {
                                  "quoted": {
                                    "type": "integer"
                                  },
                                  "visit_booked": {
                                    "type": "integer"
                                  },
                                  "won": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "quoted",
                                  "visit_booked",
                                  "won"
                                ]
                              }
                            },
                            "required": [
                              "intent",
                              "people",
                              "share_pct",
                              "previous_people",
                              "delta_pct",
                              "funnel"
                            ]
                          }
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "campaign",
                                  "ad_unmapped",
                                  "website",
                                  "organic"
                                ],
                                "description": "`campaign` a Meta campaign (ad that opened the conversation, or a website form’s first-touch UTMs) · `ad_unmapped` a Meta ad whose campaign is unknown · `website` the site with no ad behind it · `organic` a thread no ad opened (`channel` says where)."
                              },
                              "channel": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "`organic` only: the channel."
                              },
                              "campaign_external_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "campaign_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "people": {
                                "type": "integer"
                              },
                              "by_intent": {
                                "type": "object",
                                "properties": {
                                  "sell": {
                                    "type": "integer"
                                  },
                                  "trade_in": {
                                    "type": "integer"
                                  },
                                  "buy": {
                                    "type": "integer"
                                  },
                                  "unknown": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "sell",
                                  "trade_in",
                                  "buy",
                                  "unknown"
                                ],
                                "description": "People per intent."
                              },
                              "spend": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "Meta spend of the campaign over the window."
                              },
                              "cost_per_person": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                              },
                              "cost_per_intent": {
                                "type": "object",
                                "properties": {
                                  "sell": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                                  },
                                  "trade_in": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                                  },
                                  "buy": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                                  },
                                  "unknown": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                                  }
                                },
                                "required": [
                                  "sell",
                                  "trade_in",
                                  "buy",
                                  "unknown"
                                ]
                              },
                              "ad_sets": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "ad_set_external_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "ad_set_name": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "people": {
                                      "type": "integer"
                                    },
                                    "by_intent": {
                                      "type": "object",
                                      "properties": {
                                        "sell": {
                                          "type": "integer"
                                        },
                                        "trade_in": {
                                          "type": "integer"
                                        },
                                        "buy": {
                                          "type": "integer"
                                        },
                                        "unknown": {
                                          "type": "integer"
                                        }
                                      },
                                      "required": [
                                        "sell",
                                        "trade_in",
                                        "buy",
                                        "unknown"
                                      ],
                                      "description": "People per intent."
                                    }
                                  },
                                  "required": [
                                    "ad_set_external_id",
                                    "ad_set_name",
                                    "people",
                                    "by_intent"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "kind",
                              "channel",
                              "campaign_external_id",
                              "campaign_name",
                              "people",
                              "by_intent",
                              "spend",
                              "cost_per_person",
                              "cost_per_intent",
                              "ad_sets"
                            ]
                          }
                        },
                        "spend_state": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "unavailable",
                            "no_ads"
                          ],
                          "description": "`no_ads` the workspace has no Vitrina Ads · `unavailable` the Ads read failed — spend fields are `null`."
                        },
                        "spend_total": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "CLP, whole pesos. `null` — never 0 — when unknown or with no denominator."
                        },
                        "rule": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "applies",
                        "previous_window",
                        "totals",
                        "intents",
                        "entries",
                        "spend_state",
                        "spend_total",
                        "rule"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "applies": true,
                    "previous_window": {
                      "from": "2026-09-22T03:00:00.000Z",
                      "to": "2026-10-22T03:00:00.000Z"
                    },
                    "totals": {
                      "people": 120,
                      "previous_people": 96,
                      "delta_pct": 25
                    },
                    "intents": [
                      {
                        "intent": "sell",
                        "people": 70,
                        "share_pct": 58.3,
                        "previous_people": 60,
                        "delta_pct": 17,
                        "funnel": {
                          "quoted": 52,
                          "visit_booked": 9,
                          "won": 3
                        }
                      },
                      {
                        "intent": "trade_in",
                        "people": 24,
                        "share_pct": 20,
                        "previous_people": 14,
                        "delta_pct": 71,
                        "funnel": {
                          "quoted": 18,
                          "visit_booked": 4,
                          "won": 1
                        }
                      },
                      {
                        "intent": "buy",
                        "people": 26,
                        "share_pct": 21.7,
                        "previous_people": 22,
                        "delta_pct": 18,
                        "funnel": {
                          "quoted": 12,
                          "visit_booked": 3,
                          "won": 2
                        }
                      },
                      {
                        "intent": "unknown",
                        "people": 0,
                        "share_pct": 0,
                        "previous_people": 0,
                        "delta_pct": null,
                        "funnel": {
                          "quoted": 0,
                          "visit_booked": 0,
                          "won": 0
                        }
                      }
                    ],
                    "entries": [
                      {
                        "kind": "campaign",
                        "channel": null,
                        "campaign_external_id": "52595750511957",
                        "campaign_name": "Compramos tu iPhone | CL",
                        "people": 48,
                        "by_intent": {
                          "sell": 40,
                          "trade_in": 6,
                          "buy": 2,
                          "unknown": 0
                        },
                        "spend": 180000,
                        "cost_per_person": 3750,
                        "cost_per_intent": {
                          "sell": 4500,
                          "trade_in": 30000,
                          "buy": 90000,
                          "unknown": null
                        },
                        "ad_sets": [
                          {
                            "ad_set_external_id": null,
                            "ad_set_name": "Amplio - CL | ADV+",
                            "people": 48,
                            "by_intent": {
                              "sell": 40,
                              "trade_in": 6,
                              "buy": 2,
                              "unknown": 0
                            }
                          }
                        ]
                      },
                      {
                        "kind": "organic",
                        "channel": "whatsapp",
                        "campaign_external_id": null,
                        "campaign_name": null,
                        "people": 20,
                        "by_intent": {
                          "sell": 8,
                          "trade_in": 4,
                          "buy": 8,
                          "unknown": 0
                        },
                        "spend": null,
                        "cost_per_person": null,
                        "cost_per_intent": {
                          "sell": null,
                          "trade_in": null,
                          "buy": null,
                          "unknown": null
                        },
                        "ad_sets": []
                      }
                    ],
                    "spend_state": "ok",
                    "spend_total": 320000,
                    "rule": "People (distinct contacts) whose first message or lead fell in the window. …"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/overview/live": {
      "get": {
        "summary": "Current live activity",
        "description": "A now-snapshot: open conversations, who is waiting, what is in flight. The one report that takes NO range — it describes the present, so `from`/`to` would mean nothing and are not accepted.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "responses": {
          "200": {
            "description": "Live activity snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "open": 14,
                    "unattended": 0,
                    "unassigned": 1,
                    "pending": 0,
                    "agents_online": 1,
                    "agents_busy": 0,
                    "agents_offline": 4
                  },
                  "meta": {
                    "window": {
                      "kind": "instant",
                      "at": "2026-09-22T16:38:43.071Z",
                      "label": "ahora"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/overview/heatmap": {
      "get": {
        "summary": "Activity heatmap by day and hour",
        "description": "The only report that accepts `tz`. Pass the viewer’s IANA zone (`America/Santiago`) and the day/hour buckets follow their wall clock; omit it and the grid is UTC, which shifts a Chilean workspace’s busy hours by three or four columns depending on DST. The name is validated by Postgres, so an unknown zone is a 500 rather than a 400.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 64,
              "pattern": "^[A-Za-z0-9_+\\-/]+$",
              "description": "IANA timezone for the date/hour buckets. Defaults to UTC."
            },
            "required": false,
            "description": "IANA timezone for the date/hour buckets. Defaults to UTC.",
            "name": "tz",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Day/hour activity buckets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "buckets": [
                      {
                        "day": 1,
                        "hour": 10,
                        "conversations": 3
                      },
                      {
                        "day": 1,
                        "hour": 11,
                        "conversations": 5
                      }
                    ]
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/calls": {
      "get": {
        "summary": "Voice call statistics",
        "description": "Voice-channel call stats for the window. Served by the voice insights service rather than the general reporting one, so its shape does not match the other reports.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Call statistics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "range": {
                      "from": "2026-09-15T16:38:51.540Z",
                      "to": "2026-09-22T16:38:51.540Z"
                    },
                    "calls": {
                      "total": 6,
                      "answered": 5,
                      "avg_duration_s": 142,
                      "by_outcome": {
                        "completed": 5,
                        "no_answer": 1
                      },
                      "by_direction": {
                        "inbound": 4,
                        "outbound": 2
                      },
                      "by_amd": {}
                    },
                    "latency": {
                      "voice_to_voice": 820,
                      "voice_llm_turn": 410,
                      "voice_llm_first_token": 190,
                      "tool_execution": 260
                    }
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/conversations": {
      "get": {
        "summary": "Conversation volume and resolution",
        "description": "Volume, resolution and handling time over the window — the top-level view the other reports break down. `waiting_clears` counts how «Esperando respuesta» was cleared in the window: `by_jev` (the `reply_owed` verdict took a wait out), `by_button` («No requiere respuesta», `POST /conversations/{id}/no-reply-needed`; undone clicks excluded) and `corrected_to_owed` (a person answered a thread Jev had taken out, so it was owed after all); `null` when it could not be read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Conversation volume and resolution",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversations": {
                      "total": 18,
                      "delta_pct": 17,
                      "series": [
                        {
                          "bucket": "2026-09-21",
                          "value": 0
                        },
                        {
                          "bucket": "2026-09-22",
                          "value": 18
                        }
                      ]
                    },
                    "messages_in": {
                      "total": 11,
                      "delta_pct": null,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 10
                        }
                      ]
                    },
                    "messages_out": {
                      "total": 25,
                      "delta_pct": null,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 25
                        }
                      ]
                    },
                    "first_response_seconds": {
                      "avg": 728.65,
                      "delta_pct": null,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 728.65
                        }
                      ]
                    },
                    "first_response_p50_seconds": 388.36,
                    "first_response_p90_seconds": 1411.41,
                    "resolution_seconds": {
                      "avg": 9891.67,
                      "delta_pct": -91.3,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 9891.67
                        }
                      ]
                    },
                    "resolutions": {
                      "total": 5,
                      "delta_pct": -16.7,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 4
                        }
                      ]
                    },
                    "customer_wait_seconds": {
                      "avg": 728.65,
                      "delta_pct": null,
                      "series": [
                        {
                          "bucket": "2026-09-22",
                          "value": 728.65
                        }
                      ]
                    },
                    "facturables": 4,
                    "solo_humano": 14,
                    "waiting_clears": {
                      "by_jev": 9,
                      "by_button": 2,
                      "corrected_to_owed": 1
                    }
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/agents": {
      "get": {
        "summary": "Human agent performance",
        "description": "Per-human-agent throughput and response times. `/insights/ai-agents` is the machine-side counterpart; the two are not comparable row for row, because a human is assigned a conversation and an AI agent runs turns within one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Human agent performance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "name": "Carolina Muñoz",
                      "email": "carolina@example.cl",
                      "conversations": 8,
                      "replies": 5,
                      "avg_first_response_seconds": 733.76,
                      "avg_response_seconds": 28197.49,
                      "avg_resolution_seconds": 9891.67,
                      "avg_customer_wait_seconds": 733.76,
                      "resolutions": 4
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/ai-agents": {
      "get": {
        "summary": "Per-AI-agent performance breakdown",
        "description": "One row per `ai_agent`. `/insights/bots` answers the same question in aggregate — use that for a single headline number and this to find which agent moved it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-AI-agent performance breakdown",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "ai_agent_id": "a3a3a3a3-0000-4000-8000-000000000001",
                      "name": "Asistente de ventas",
                      "status": "active",
                      "is_default": true,
                      "conversations": 12,
                      "responses": 30,
                      "avg_response_seconds": 4.2,
                      "automated_conversations": 8,
                      "handoffs": 2,
                      "resolutions": 4,
                      "tool_calls": 21,
                      "avg_gen_latency_ms": 7938,
                      "handoffs_requested": 2,
                      "handoffs_answered": 2
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/bots": {
      "get": {
        "summary": "Aggregate AI handling",
        "description": "AI handling across the workspace as one figure, not split by agent. The aggregate counterpart to `/insights/ai-agents`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Aggregate AI handling",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "conversations": 12,
                    "automated_conversations": 8,
                    "total_conversations": 18,
                    "engaged_conversations": 11,
                    "deflection_rate": 0.44,
                    "total_responses": 30,
                    "avg_response_seconds": 4.2,
                    "resolutions": 4,
                    "handoff_rate": 0.17,
                    "resolved_by_bot": 3,
                    "resolved_by_human": 1,
                    "resolved_by_auto": 0,
                    "avg_gen_latency_ms": 7938,
                    "tool_calls": 21,
                    "resolutions_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 3
                      }
                    ],
                    "handoff_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 1
                      }
                    ]
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/tags": {
      "get": {
        "summary": "Volume by tag",
        "description": "Conversation counts grouped by the tags applied to them.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Volume by tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "__none__",
                      "label": "Sin etiqueta",
                      "conversations": 17,
                      "avg_first_response_seconds": 728.65,
                      "avg_resolution_seconds": 9891.67,
                      "avg_customer_wait_seconds": 728.65,
                      "resolutions": 4
                    },
                    {
                      "key": "e1e1e1e1-9000-4000-8000-000000000001",
                      "label": "garantia",
                      "conversations": 1,
                      "avg_first_response_seconds": 0,
                      "avg_resolution_seconds": 0,
                      "avg_customer_wait_seconds": 0,
                      "resolutions": 0
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/channels": {
      "get": {
        "summary": "Volume by channel",
        "description": "Grouped by messaging channel (WhatsApp, Instagram, email, voice, …), so a channel with no traffic in the window is simply absent rather than present with a zero.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Volume by channel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "00000000-0000-4000-8000-000000005001",
                      "label": "WhatsApp · Acme",
                      "conversations": 5,
                      "avg_first_response_seconds": 450.43,
                      "avg_resolution_seconds": 101.4,
                      "avg_customer_wait_seconds": 450.43,
                      "resolutions": 1
                    },
                    {
                      "key": "__none__",
                      "label": "Directo / sin canal",
                      "conversations": 4,
                      "avg_first_response_seconds": 0,
                      "avg_resolution_seconds": 0,
                      "avg_customer_wait_seconds": 0,
                      "resolutions": 0
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/teams": {
      "get": {
        "summary": "Volume by team",
        "description": "Conversation counts grouped by the team they were routed to.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Volume by team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "__none__",
                      "label": "Sin equipo",
                      "conversations": 18,
                      "avg_first_response_seconds": 728.65,
                      "avg_resolution_seconds": 9891.67,
                      "avg_customer_wait_seconds": 728.65,
                      "resolutions": 4
                    },
                    {
                      "key": "cccccccc-0000-4000-8000-000000000001",
                      "label": "Marketing",
                      "conversations": 0,
                      "avg_first_response_seconds": 0,
                      "avg_resolution_seconds": 0,
                      "avg_customer_wait_seconds": 0,
                      "resolutions": 0
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/sources": {
      "get": {
        "summary": "Volume by origin",
        "description": "The same row shape as `/insights/channels`, grouped by where the conversation CAME FROM (`mercadolibre`, `yapo`, `chileautos`, `ai_agent`, `website`, …) rather than which messaging account it rode in on. The two are not interchangeable: marketplace leads all arrive on the same WhatsApp number, so `/insights/channels` collapses every paid origin into one row. Unlike channels there is no catalogue to zero-fill from, so an origin with no traffic is absent rather than present with a zero; `__none__` is the no-origin bucket.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Volume by origin",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "__none__",
                      "label": "Directo / sin origen",
                      "conversations": 14,
                      "avg_first_response_seconds": 728.65,
                      "avg_resolution_seconds": 9891.67,
                      "avg_customer_wait_seconds": 728.65,
                      "resolutions": 4
                    },
                    {
                      "key": "website",
                      "label": "Sitio web",
                      "conversations": 4,
                      "avg_first_response_seconds": 0,
                      "avg_resolution_seconds": 0,
                      "avg_customer_wait_seconds": 0,
                      "resolutions": 0
                    }
                  ],
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/leads": {
      "get": {
        "summary": "Lead capture",
        "description": "Leads created over the window and where they came from.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead capture",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "open": 16,
                    "won": 2,
                    "lost": 1,
                    "unqualified": 0,
                    "total_value_open": 19407775,
                    "total_value_won": 4500000,
                    "total_value_open_by_currency": {
                      "CLP": 19399000
                    },
                    "total_value_won_by_currency": {
                      "CLP": 4500000
                    },
                    "is_mixed_currency": false,
                    "is_mixed_currency_open": false,
                    "win_rate": 0.67,
                    "avg_cycle_seconds": 432000,
                    "created_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 6
                      }
                    ],
                    "won_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 1
                      }
                    ],
                    "revenue_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 4500000,
                        "value_by_currency": {
                          "CLP": 4500000
                        },
                        "is_mixed_currency": false
                      }
                    ],
                    "funnel": [
                      {
                        "stage_id": "55555555-0000-4000-8000-000000000001",
                        "stage_name": "Nuevo",
                        "position": 0,
                        "open_count": 9,
                        "total_value": 19401030,
                        "total_value_by_currency": {
                          "CLP": 19399000
                        },
                        "is_mixed_currency": false,
                        "median_time_in_stage_hours": 6.5
                      }
                    ]
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/csat": {
      "get": {
        "summary": "Estimated satisfaction",
        "description": "CSAT is AI-ESTIMATED from the conversation, not collected from a survey — no customer was asked to rate anything. Read it as a model judgement about tone and outcome, which is why it exists for every conversation rather than the small fraction that would answer a survey.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Estimated satisfaction",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total_responses": 9,
                    "satisfaction_score": 0.82,
                    "response_rate": 0.5,
                    "positive": 7,
                    "neutral": 1,
                    "negative": 1,
                    "recommend_rate": 0.78,
                    "avg_confidence": 0.9,
                    "review_requests_sent": 18,
                    "resolved_tickets": 5,
                    "distribution": {
                      "positive": 7,
                      "neutral": 1,
                      "negative": 1
                    },
                    "resolution_quality": {
                      "good": 8,
                      "poor": 1
                    },
                    "agent_performance": {},
                    "recent": []
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/sla": {
      "get": {
        "summary": "SLA compliance",
        "description": "Breach and compliance counts against the workspace SLA policies. A window with no policy configured reports nothing to comply with, not full compliance.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "24h",
                "7d",
                "30d",
                "90d",
                "calendar_month",
                "calendar_year",
                "custom"
              ],
              "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed."
            },
            "required": false,
            "description": "Which period. Omit it (with `from`/`to`) for an explicit range — the shape every ranged Insights report has always accepted, and which still works unchanged. The calendar windows are the DEALERSHIP’s (America/Santiago), so a month closes when a person would say it closed.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress."
            },
            "required": false,
            "description": "`window=calendar_month` only. ISO month, `YYYY-MM`. Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}$",
              "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress."
            },
            "required": false,
            "description": "`window=calendar_year` only. `YYYY`. Defaults to the year in progress.",
            "name": "year",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant."
            },
            "required": false,
            "description": "Explicit range only. Checked for non-emptiness and handed to Postgres, so a malformed value surfaces as a 500 from the query rather than a 400 from the edge — send an ISO-8601 instant.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Explicit range only. ISO instant, see `from`."
            },
            "required": false,
            "description": "Explicit range only. ISO instant, see `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "SLA compliance",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "has_policies": true,
                    "policy_count": 2,
                    "target_first_response_minutes": 15,
                    "target_resolution_minutes": 480,
                    "tracked": 18,
                    "breached": 2,
                    "hits": 16,
                    "hit_rate": 0.89,
                    "first_response": {
                      "evaluated": 18,
                      "breached": 1,
                      "hit_rate": 0.94
                    },
                    "resolution": {
                      "evaluated": 5,
                      "breached": 1,
                      "hit_rate": 0.8
                    },
                    "by_policy": [
                      {
                        "policy_id": "cccccccc-1000-4000-8000-000000000001",
                        "name": "SLA estándar",
                        "hit_rate": 0.89
                      }
                    ],
                    "breaches": []
                  },
                  "meta": {
                    "window": {
                      "kind": "rolling",
                      "preset": "7d",
                      "hours": 168,
                      "from": "2026-09-15T16:38:51.000Z",
                      "to": "2026-09-22T16:38:51.000Z",
                      "label": "últimos 7d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/storefront": {
      "get": {
        "summary": "Storefront performance for a calendar month",
        "description": "The one ranged report that does NOT take the `?window=` vocabulary: it is monthly by construction, so it takes `?month=YYYY-MM` and nothing else, defaulting to the month in progress. The month is the DEALERSHIP’s (America/Santiago), and the resolved bounds come back in `meta.window`.\n\n⚠ **`analytics:read` buys the numbers, not the people.** The `recent_leads` rows carry `contact_name`/`contact_phone`, which are the contact’s own — without `contacts:read` both keys are DELETED from each row (absent, never null: what is missing is the reader, not the data). The MCP twin applies the same projection, so neither surface is the loose one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "ISO month, `YYYY-MM`, in the DEALERSHIP’s zone (America/Santiago). Defaults to the month in progress."
            },
            "required": false,
            "description": "ISO month, `YYYY-MM`, in the DEALERSHIP’s zone (America/Santiago). Defaults to the month in progress.",
            "name": "month",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Storefront traffic, leads and conversion for the month",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "storefront_visits": 240,
                    "storefront_leads": 4,
                    "conversion": {
                      "value": 0.017,
                      "numerator": {
                        "name": "storefront_leads",
                        "count": 4
                      },
                      "denominator": {
                        "name": "storefront_visits",
                        "count": 240
                      }
                    },
                    "visits_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 12
                      }
                    ],
                    "leads_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 1
                      }
                    ],
                    "top_vehicles": [
                      {
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "views": 30
                      }
                    ],
                    "recent_leads": [
                      {
                        "lead_id": "99999999-0000-4000-8000-000000000001",
                        "created_at": "2026-09-22T12:40:29.275Z",
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "vehicle_label": "Peugeot 2008 2024"
                      }
                    ],
                    "window": {
                      "kind": "calendar_month",
                      "month": "2026-09",
                      "from": "2026-09-01T04:00:00.000Z",
                      "to": "2026-10-01T03:00:00.000Z",
                      "tz": "America/Santiago",
                      "label": "septiembre 2026",
                      "as_of": "2026-09-22T16:38:51.570Z"
                    }
                  },
                  "meta": {
                    "window": {
                      "kind": "calendar_month",
                      "month": "2026-09",
                      "from": "2026-09-01T04:00:00.000Z",
                      "to": "2026-10-01T03:00:00.000Z",
                      "tz": "America/Santiago",
                      "label": "septiembre 2026",
                      "as_of": "2026-09-22T16:38:51.570Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/storefront/card": {
      "get": {
        "summary": "Storefront summary for the CRM dashboard \"Mi sitio\" card",
        "description": "A compact, ROLLING sibling of `GET /insights/storefront` (ticket 86e2rwqxw): a FIXED trailing 30-day period, plus the immediately-preceding 30-day period for a period-over-period comparison. Takes no query parameters — the window is not caller-chosen, unlike the calendar-month report. Returns `visits`/`leads` as `{current, previous}` raw counts (never a pre-computed delta), a zero-filled `visits_series` sparkline, and the top 5 (not 10) most-viewed vehicles. Carries NO personal data — no `recent_leads`, no contact fields — so unlike the full report this endpoint needs no `contacts:read` branch. Site status (published/draft, the dealer’s own domain) is deliberately not here: `GET /tenant/sitio` and `GET /tenant/settings` already serve it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "responses": {
          "200": {
            "description": "Storefront visits/leads/top-vehicles for the trailing 30 days, vs. the prior 30 days",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "period": {
                      "from": "2026-08-23T16:38:51.606Z",
                      "to": "2026-09-22T16:38:51.606Z",
                      "label": "últimos 30d"
                    },
                    "previous_period": {
                      "from": "2026-07-24T16:38:51.606Z",
                      "to": "2026-08-23T16:38:51.606Z",
                      "label": "período anterior"
                    },
                    "visits": {
                      "current": 240,
                      "previous": 180
                    },
                    "leads": {
                      "current": 4,
                      "previous": 2
                    },
                    "visits_series": [
                      {
                        "bucket": "2026-09-22",
                        "value": 12
                      }
                    ],
                    "top_vehicles": [
                      {
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "views": 30
                      }
                    ],
                    "as_of": "2026-09-22T16:38:51.606Z"
                  },
                  "meta": {
                    "period": {
                      "from": "2026-08-23T16:38:51.606Z",
                      "to": "2026-09-22T16:38:51.606Z",
                      "label": "últimos 30d"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/insights/generate-pdf": {
      "post": {
        "summary": "Render a report as a PDF",
        "description": "**The one endpoint on the tenant API that does not answer the `{ data }` envelope.** It replies with raw `application/pdf` bytes and a `Content-Disposition: attachment` header, so a client that pipes every response through the usual JSON parse will fail here. On an error it reverts to the JSON error envelope, which means the response content-type is what tells the two apart.\n\nGeneration is SYNCHRONOUS: it queries the report, runs an LLM pass to write the narrative, then renders. The connection is held for all of that, there is no job id, and nothing is persisted — a client or proxy timeout loses the document with no way to retrieve it other than asking again. Treat a wide range as a slow request.\n\nThe LLM pass degrades rather than fails: if the narrative cannot be generated you still get a 200 and a complete PDF, with a Spanish note in place of the executive summary and every raw figure intact. So a successful response does not by itself mean the analysis is there.\n\n`range.preset` is not a range selector — `from`/`to` alone decide the data. It is the window’s NAME: printed as the cover subtitle and passed into the summariser’s prompt, so a preset that disagrees with `from`/`to` mislabels the cover and misleads the narrative while the figures stay correct. Defaults to `custom`. Needs only `analytics:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "report_type": {
                    "type": "string",
                    "enum": [
                      "overview",
                      "conversations",
                      "agents",
                      "ai_agents",
                      "bots",
                      "tags",
                      "channels",
                      "teams",
                      "leads",
                      "csat",
                      "sla",
                      "clinic_ocupacion",
                      "clinic_inasistencias",
                      "clinic_produccion",
                      "clinic_packs"
                    ]
                  },
                  "range": {
                    "type": "object",
                    "properties": {
                      "from": {
                        "type": "string",
                        "minLength": 1
                      },
                      "to": {
                        "type": "string",
                        "minLength": 1
                      },
                      "preset": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "from",
                      "to"
                    ]
                  }
                },
                "required": [
                  "report_type",
                  "range"
                ]
              },
              "example": {
                "report": "conversations",
                "window": {
                  "preset": "7d"
                },
                "range": {
                  "preset": "7d"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rendered PDF as an attachment",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/insights/schedules": {
      "get": {
        "summary": "List report schedules",
        "description": "The standing subscriptions that mail a rendered report out on a cadence. Unpaginated, and with no `meta.total` — this list is small by construction.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Analytics"
        ],
        "responses": {
          "200": {
            "description": "Report schedules",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "cccccccc-2000-4000-8000-000000000001",
                      "report_type": "conversations",
                      "range_preset": "7d",
                      "cadence": "weekly",
                      "recipients": [
                        "gerencia@midealer.cl"
                      ],
                      "next_run_at": "2026-09-29T09:00:00.000Z",
                      "enabled": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/meta-ads": {
      "get": {
        "summary": "Read the workspace’s Meta Ads connection",
        "description": "The tenant’s single Meta Ads row, or `null` when it has never started a connect. A placeholder object with `status: \"disconnected\"` is deliberately NOT returned in its place: \"never connected\" and \"was connected and is not any more\" are different states and the card says different things about them.\n\n`handoff` is populated only while `status` is `pending` and `candidates` only while it is `pending_selection` — a spent consent URL would render a button leading to a dead page.\n\nRequires `integrations:read` or `ads:read` (owner, admin and the Marketing role); a `supervisor` or `agent` gets 403 here and never sees the card. The writes need `integrations:write` or `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "The Meta Ads integration, or `null`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MetaAdsIntegration"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": null
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/meta-ads/connect": {
      "post": {
        "summary": "Start a Meta Ads connect and get the consent URL",
        "description": "Mints the connection provider’s **connect hand-off** for `meta_ads` and answers the session-less consent URL to redirect the WHOLE PAGE to. It is not an iframe target: Meta’s own consent dialog renders inside it.\n\n`return_to` must be an absolute URL on the app’s own origin. The backend appends `meta_ads=return` and a short opaque `state`, preserving any query the URL already carried, and passes the result to the provider as `return_url`. An origin that is not the app’s is a 400 here rather than a silent substitution — the provider itself only checks the origin when the human CLICKS, where it surfaces as a failed hand-off (`result.reason: origin_not_allowed`) after the dealer has already walked through a consent screen for nothing.\n\n**A second click reuses the live hand-off.** The provider’s URL is single-use and 45-minute-lived, and minting twice yields two hand-offs rather than an error, so a dealer who clicks, gets distracted and clicks again gets the SAME `url` and `handoff_id` back until it expires.\n\nBefore anything is minted, the tenant’s attribution profile and its delegated key are ensured (ADR 0099). This rail never uses a platform-wide provider key.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MetaAdsConnectBody"
              },
              "example": {
                "return_to": "https://app.vitrinadev.com/w/agencia/settings/integrations"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The consent URL, its hand-off id, and its expiry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MetaAdsConnectResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "url": "https://consent.example.com/connect/meta-ads/ho_4d8f2a91c6b3",
                    "handoff_id": "ho_4d8f2a91c6b3",
                    "expires_at": "2026-09-23T15:45:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/meta-ads/return": {
      "post": {
        "summary": "Settle a Meta Ads connect after the dealer comes back",
        "description": "Reads the hand-off ONCE and settles the row:\n\n* `completed` with a connection id → the connection is read back from the provider and the row becomes `connected`, carrying the ad account’s name, id, currency, timezone and whether the platform has ever delivered spend.\n* `completed` with a pending selection → the row becomes `pending_selection` and carries the candidates the picker renders. **A consent that resolved to exactly one account never reaches this branch** — the provider finalizes it in its own callback, which is why there is no auto-finalize step on this side.\n* `expired` / `cancelled` / `failed` → the row becomes `error` with a retryable `reason` of `handoff_<status>`. The recovery is identical for all three: mint a fresh hand-off.\n* still `pending` → nothing is settled and the row comes back unchanged.\n\n**Neither field is required.** The hand-off id only exists in the RESPONSE to the mint while `return_url` is an argument to that same mint, so the return URL structurally cannot carry it — it carries `meta_ads=return` and the opaque `state` instead. Send `handoff_id` if you kept it, `state` if you read it off the URL, or an empty body, in which case the backend settles the row’s own pending hand-off (unambiguous: a tenant has at most one).\n\nSafe to repeat — a settled hand-off stays readable at the provider, so a re-post on a refresh re-derives the same state.\n\n409 `handoff_mismatch` when the id or state presented does not name the hand-off this tenant’s row is waiting on, and for a tenant with no connect in flight at all (the two are deliberately the same answer).\n\n502 `UPSTREAM_ERROR` when the provider could not be read; the row is left unchanged and the call is safe to repeat.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MetaAdsReturnBody"
              },
              "example": {
                "state": "9f3a2d7c8b1e4a2f"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settled integration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/MetaAdsIntegration"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "22222222-2222-4222-8222-222222222222",
                    "provider": "meta_ads",
                    "status": "connected",
                    "account_label": "Cuenta Meta Ads",
                    "connected_at": "2026-09-23T15:04:00.000Z",
                    "disconnected_at": null,
                    "account": {
                      "external_account_id": "act_1234567890",
                      "name": "Automotora Ejemplo",
                      "currency": "CLP",
                      "timezone": "America/Santiago",
                      "delivers_spend": true,
                      "provider_connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                      "atribu_connection_id": "c0c0c0c0-0000-4000-8000-000000000001"
                    },
                    "error": null,
                    "handoff": null,
                    "candidates": null,
                    "updated_at": "2026-09-23T15:04:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/meta-ads/disconnect": {
      "post": {
        "summary": "Disconnect the workspace’s Meta Ads ad account",
        "description": "Calls the provider’s disconnect for the connection on the delegated key and settles the row `disconnected`, keeping its history: `account_label` and `connected_at` stay, and `config.last_account` remembers which account this was so the card can still name it. Reconnecting afterwards is a plain new `/connect` — it mints a fresh hand-off. A tenant must always be able to walk away from a connection it made.\n\nA row with nothing at the provider to revoke — `pending` / `pending_selection` with no connection — is cancelled LOCALLY: no provider call, the row moves straight to `disconnected`, and the pending hand-off simply expires on its own (the provider has no endpoint to cancel one early). A row already `disconnected` answers 200 unchanged (idempotent).\n\n409 `sync_in_flight` when the provider is mid-sync on the connection — the row is left exactly as it was; the whole recovery is trying again in a few minutes. 502 `disconnect_refused` when the provider’s own 403 says the delegated key is no longer good for this connection — also left unchanged. A 404 from the provider (the connection is already gone there) is treated as success: the state this call exists to ensure already holds.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "The disconnected integration",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/MetaAdsIntegration"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "22222222-2222-4222-8222-222222222222",
                    "provider": "meta_ads",
                    "status": "disconnected",
                    "account_label": "Cuenta Meta Ads",
                    "connected_at": "2026-09-20T12:00:00.000Z",
                    "disconnected_at": "2026-09-23T15:10:00.000Z",
                    "account": null,
                    "error": null,
                    "handoff": null,
                    "candidates": null,
                    "updated_at": "2026-09-23T15:10:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/meta-ads/finalize": {
      "post": {
        "summary": "Finalize a Meta Ads pending selection with a chosen candidate",
        "description": "Only meaningful while `status` is `pending_selection`. Calls the provider’s finalize on the parked provider token with the chosen `candidate_id` — a `MetaAdsCandidate.candidate_id` from the SAME row’s `candidates`, sent back verbatim — reads the resulting connection back (the provider’s Meta Ads connections read) and runs it through the same settle path `/return` uses, so the row reads identically whichever produced it: `connected`, with `account` populated.\n\n**Retrying the same `candidate_id` is safe** and answers 200 with the current row — the provider’s `already_finalized: true` on its own idempotent replay, reapplied through the same write path a fresh finalize uses.\n\n**A DIFFERENT `candidate_id`** after the provider’s parked token already finalized a choice is 409 `start_again` (the row moves to `error`, `reason: selection_conflict`, retryable) — switching accounts needs a fresh connect, there is no token left to retarget.\n\n**The picker’s 60-minute parked token already gone** (elapsed, or never existed for this profile) is 410 `reconnect` (the row moves to `error`, `reason: selection_expired`, retryable).\n\n**The row is not `pending_selection`** — nothing started, already connected, or already in `error` — is 409 `handoff_mismatch`, checked before the provider is ever called.\n\n502 `UPSTREAM_ERROR` when the provider could not be read; the row is left unchanged and the call is safe to repeat.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MetaAdsFinalizeBody"
              },
              "example": {
                "candidate_id": "cnd_7b1f0a4e9d2c"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The finalized integration, now `connected`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/MetaAdsIntegration"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "22222222-2222-4222-8222-222222222222",
                    "provider": "meta_ads",
                    "status": "connected",
                    "account_label": "Cuenta Meta Ads",
                    "connected_at": "2026-09-23T15:04:00.000Z",
                    "disconnected_at": null,
                    "account": {
                      "external_account_id": "act_1234567890",
                      "name": "Automotora Ejemplo",
                      "currency": "CLP",
                      "timezone": "America/Santiago",
                      "delivers_spend": true,
                      "provider_connection_id": "c0c0c0c0-0000-4000-8000-000000000001",
                      "atribu_connection_id": "c0c0c0c0-0000-4000-8000-000000000001"
                    },
                    "error": null,
                    "handoff": null,
                    "candidates": null,
                    "updated_at": "2026-09-23T15:04:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "The picker’s parked provider token is gone — `{ error: { code: \"reconnect\" } }`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/meta-ads/pages": {
      "get": {
        "summary": "The Facebook Pages the Meta account can see, and which are tracked",
        "description": "Tracking a Page is what lets an ad video owned by that Page play inside the product instead of falling back to an embed. Pages are read live from Meta with the token the Meta Ads connection already holds — including Pages owned by a Business portfolio the Facebook user holds no direct role on.\n\n`needs_reconsent: true` (with an empty `available`) is NOT an error: the Meta grant lacks the Page permissions (an older consent) or Meta no longer accepts the token. Restart `POST /integrations/meta-ads/connect` to grant them again; `connected` still lists what was already tracked.\n\n409 `meta_ads_not_connected` when no ad account is connected. 502 `UPSTREAM_ERROR` when the provider could not answer.\n\nRequires `integrations:read` or `ads:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "Available and tracked Pages, or the re-consent signal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MetaAdsPagesState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": [
                      {
                        "page_external_id": "109876543212345",
                        "name": "Clínica Ejemplo",
                        "picture_url": "https://cdn.example.com/pages/clinica.jpg",
                        "instagram_username": "clinica.ejemplo",
                        "connected": true
                      }
                    ],
                    "connected": [
                      {
                        "page_external_id": "109876543212345",
                        "name": "Clínica Ejemplo",
                        "picture_url": "https://cdn.example.com/pages/clinica.jpg",
                        "instagram_username": "clinica.ejemplo"
                      }
                    ],
                    "needs_reconsent": false,
                    "reconsent_reason": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Choose the Facebook Pages to track",
        "description": "Tracks exactly the Pages in `page_external_ids`; Pages left out stop being tracked (nothing is deleted, so re-adding one is instant). Ids the Meta account cannot see are not saved and come back in `unavailable_page_external_ids`.\n\n409 `meta_ads_not_connected` when no ad account is connected. 502 `UPSTREAM_ERROR` when the provider could not answer.\n\nRequires `integrations:write` or `ads:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MetaAdsSetPagesBody"
              },
              "example": {
                "page_external_ids": [
                  "109876543212345"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many Pages are tracked now",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MetaAdsSetPagesResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enabled_count": 1,
                    "unavailable_page_external_ids": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/meta-ads/ads": {
      "get": {
        "summary": "The tenant's click-to-WhatsApp ads, with connect/greeting checks",
        "description": "Reads the partner click-to-WhatsApp ads list with the tenant’s WhatsApp **partner** token — a different credential than the connect rail above’s delegated key — and derives two facts per ad: `checks.destination_connected` (the ad’s WhatsApp number, compared as E.164, is one of the tenant’s connected WhatsApp messaging accounts) and `checks.greeting_configured` (a \"Saludo automático\" — text or tappable ice breakers — is actually set).\n\nRequires `integrations:read` or `ads:read` (owner, admin and the Marketing role — not \"any member\"), free for every tenant (no Add-on gate).\n\n`data.state` is `not_connected` in TWO cases, told apart by an optional `reason`:\n* no `reason` — the tenant has never connected a Meta Ads account (the integration row is missing or not `connected`).\n* `reason: \"whatsapp_not_connected\"` — Meta Ads may be connected, but the tenant holds no `whatsapp_cloud` account carrying an partner token, so there is no scope to list ads under.\n\n`cursor` is the provider’s own opaque keyset cursor (its `pagination.cursor`, a Meta ad id ascending), passed straight through as `after`. `next_cursor` is `null` once the provider reports `has_next: false`. `last_synced_at` is the max over the returned PAGE only, not a tenant-wide sync timestamp.\n\n502 `UPSTREAM_ERROR` on a provider 5xx, timeout, or a response shape this route does not recognise. 502 `partner_token_invalid` when the provider answers 401 on the WhatsApp partner token — that token is never auto-rotated; the dealer must reconnect WhatsApp.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A `not_connected` state (with or without a `reason`), or the connected page of ads",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MetaAdsAdsResponse"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "connected",
                    "items": [
                      {
                        "ad_id": "1234567890123",
                        "ad_name": "Test drive fin de semana",
                        "ad_status": "ACTIVE",
                        "campaign": {
                          "id": "6543210987654",
                          "name": "Leads · WhatsApp"
                        },
                        "ad_set": {
                          "id": "6543210987655",
                          "name": "Región Metropolitana"
                        },
                        "destination": {
                          "type": "whatsapp",
                          "whatsapp_phone_number": "+56912345678",
                          "page_id": "998877665544"
                        },
                        "cta_type": "WHATSAPP_MESSAGE",
                        "greeting": {
                          "text": "¡Hola! Cuéntanos qué auto buscas.",
                          "autofill_message": "Quiero agendar un test drive",
                          "ice_breakers": [
                            "Ver stock disponible",
                            "Agendar test drive"
                          ],
                          "landing_screen_type": "welcome_message"
                        },
                        "greeting_status": "present",
                        "checks": {
                          "destination_connected": true,
                          "greeting_configured": true
                        },
                        "last_synced_at": "2026-09-23T14:00:00.000Z"
                      }
                    ],
                    "next_cursor": null,
                    "last_synced_at": "2026-09-23T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/woocommerce": {
      "get": {
        "summary": "Read the workspace’s WooCommerce connection",
        "description": "The connection card: the store, whether it is connected, the last health check (`health.failure` names what blocks it: `not_https`, `permalinks_plain`, `rest_api_blocked`, `woocommerce_missing`, `credentials_rejected`, `webhooks_inactive`, … with a Spanish `health.message` saying how to fix it), `warnings` (things that work but need attention, e.g. `webhooks_delayed` when the store’s WordPress cron delivers stock changes late), the webhooks registered at the store and the last import/reconcile. `status: \"not_connected\"` when the workspace never started a connect. Requires `integrations:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "status": "connected",
                    "store_url": "https://tienda.example.cl",
                    "wc_version": "9.8.5",
                    "currency": "CLP",
                    "key_preview": "ck_a••••9f",
                    "health": {
                      "ok": true,
                      "failure": null,
                      "message": "Tu tienda está conectada y sincronizando.",
                      "detail": null,
                      "warnings": [],
                      "store_url": "https://tienda.example.cl",
                      "wc_version": "9.8.5",
                      "checked_at": "2026-10-15T14:00:00.000Z"
                    },
                    "webhooks": [
                      {
                        "external_id": "41",
                        "topic": "product.created",
                        "status": "active"
                      },
                      {
                        "external_id": "42",
                        "topic": "product.updated",
                        "status": "active"
                      },
                      {
                        "external_id": "43",
                        "topic": "product.deleted",
                        "status": "active"
                      },
                      {
                        "external_id": "44",
                        "topic": "product.restored",
                        "status": "active"
                      },
                      {
                        "external_id": "45",
                        "topic": "order.created",
                        "status": "active"
                      },
                      {
                        "external_id": "46",
                        "topic": "order.updated",
                        "status": "active"
                      }
                    ],
                    "connected_at": "2026-10-15T13:58:12.000Z",
                    "last_event_at": "2026-10-15T14:05:40.000Z",
                    "last_event_lag_seconds": 12,
                    "warnings": [],
                    "last_sync_at": "2026-10-15T14:00:03.000Z",
                    "last_sync_status": "success",
                    "last_sync_error": null,
                    "last_sync_summary": {
                      "products": 48,
                      "variants": 131,
                      "skipped": 1,
                      "deactivated": 0,
                      "blocked": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/woocommerce/connect": {
      "post": {
        "summary": "Start connecting a WooCommerce store",
        "description": "Runs the connection health check against the store first. When it passes, returns `authorize_url`: WooCommerce’s own approval screen (`/wc-auth/v1/authorize`) for a HUMAN to open in the browser — the store then sends Vitrina a read/write REST key and the import starts. When it fails, `authorize_url` is null and `health.failure` names the cause. 409 when the workspace already has a different store connected. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WooCommerceConnectBody"
              },
              "example": {
                "store_url": "tienda.example.cl"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The approval URL, or the named failure",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "authorize_url": "https://tienda.example.cl/wc-auth/v1/authorize?app_name=Vitrina&scope=read_write&user_id=…&return_url=…&callback_url=…",
                    "health": {
                      "ok": true,
                      "failure": null,
                      "message": "Tu tienda está lista para conectarse.",
                      "detail": null,
                      "warnings": [],
                      "store_url": "https://tienda.example.cl",
                      "wc_version": null,
                      "checked_at": "2026-10-15T14:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/woocommerce/check": {
      "post": {
        "summary": "Check the WooCommerce connection again",
        "description": "«Revisar conexión». For a connected store: re-activates or re-creates Vitrina’s webhooks there and re-checks the key, version and currency. For a pending one: re-runs the reachability probe. Returns the card. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "connected": true,
                    "status": "connected",
                    "store_url": "https://tienda.example.cl",
                    "wc_version": "9.8.5",
                    "currency": "CLP",
                    "key_preview": "ck_a••••9f",
                    "health": {
                      "ok": true,
                      "failure": null,
                      "message": "Tu tienda está conectada y sincronizando.",
                      "detail": null,
                      "warnings": [],
                      "store_url": "https://tienda.example.cl",
                      "wc_version": "9.8.5",
                      "checked_at": "2026-10-15T14:00:00.000Z"
                    },
                    "webhooks": [
                      {
                        "external_id": "41",
                        "topic": "product.created",
                        "status": "active"
                      },
                      {
                        "external_id": "42",
                        "topic": "product.updated",
                        "status": "active"
                      },
                      {
                        "external_id": "43",
                        "topic": "product.deleted",
                        "status": "active"
                      },
                      {
                        "external_id": "44",
                        "topic": "product.restored",
                        "status": "active"
                      },
                      {
                        "external_id": "45",
                        "topic": "order.created",
                        "status": "active"
                      },
                      {
                        "external_id": "46",
                        "topic": "order.updated",
                        "status": "active"
                      }
                    ],
                    "connected_at": "2026-10-15T13:58:12.000Z",
                    "last_event_at": "2026-10-15T14:05:40.000Z",
                    "last_event_lag_seconds": 12,
                    "warnings": [],
                    "last_sync_at": "2026-10-15T14:00:03.000Z",
                    "last_sync_status": "success",
                    "last_sync_error": null,
                    "last_sync_summary": {
                      "products": 48,
                      "variants": 131,
                      "skipped": 1,
                      "deactivated": 0,
                      "blocked": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/woocommerce/sync": {
      "post": {
        "summary": "Sincronizar ahora — reconcile the store’s catalog",
        "description": "Enqueues a full walk of the store (the same job the hourly reconcile runs): products, variations and stock, then products the store no longer lists are switched off — unless that looks like a mass drop, in which case nothing is switched off (`last_sync_status: \"blocked\"`) until `force: true`. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WooCommerceSyncBody"
              },
              "example": {
                "force": false
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enqueued": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/woocommerce/disconnect": {
      "post": {
        "summary": "Disconnect the WooCommerce store",
        "description": "Removes Vitrina’s webhooks from the store, forgets the key and stops offering the store’s products (their stock is no longer live). The products are kept; reconnecting the same store switches the live ones back on. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "Disconnected",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "products_deactivated": 48
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/integrations/external-stock-source": {
      "get": {
        "summary": "Read the workspace’s external stock source",
        "description": "The back-office stock endpoint the «Stock faltante» alert asks live: its URL and a masked preview of its token (never the token). `configured: false` until a URL and a token are saved. Requires `integrations:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "The stock source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "url": "https://backoffice.example.cl/api/vitrina/stock",
                    "token_preview": "a1b2••••9z",
                    "updated_at": "2026-10-28T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Save the workspace’s external stock source",
        "description": "Sets the endpoint (`url`, absolute https) and its token (`token`, sent as the `x-vitrina-token` header). Omit `token` to keep the stored one; it is required the first time. The endpoint must answer `{ conteos: [{ modelo, capacidad, gb, cantidad }] }` (or the matrix `{ modelos: [{ modelo, combinaciones: [{ capacidad, cantidad }] }] }`); a model or capacity it does not list has zero units. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalStockSourceBody"
              },
              "example": {
                "url": "https://backoffice.example.cl/api/vitrina/stock",
                "token": "el-token-del-backoffice"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved stock source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "configured": true,
                    "url": "https://backoffice.example.cl/api/vitrina/stock",
                    "token_preview": "a1b2••••9z",
                    "updated_at": "2026-10-28T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove the workspace’s external stock source",
        "description": "Deletes the URL and the stored token. The «Stock faltante» alert then says «no pudimos verificar el stock». Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "Whether a source was removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "removed": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections": {
      "get": {
        "summary": "List the workspace’s provider connections (CRM, payments)",
        "description": "One card per connectable provider — GoHighLevel, Mercado Pago, Shopify — ALWAYS every provider, never-connected ones included (`state: \"not_connected\"`), so an integrations screen renders from this one read. Each card is exactly what `GET /integrations/connections/{provider}` returns.\n\nRequires `integrations:read` (owner and admin).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "responses": {
          "200": {
            "description": "Every provider connection card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProviderConnection"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "provider": "gohighlevel",
                      "display_name": "GoHighLevel",
                      "category": "crm",
                      "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                      "last_account": null,
                      "last_attempt": null,
                      "disconnected_at": null,
                      "connect_input": "none",
                      "connect_notice": null,
                      "catalog": null,
                      "addon_active": true,
                      "state": "connecting",
                      "state_label": "Elige la subcuenta",
                      "account": null,
                      "reason": null,
                      "next_step": {
                        "action": "choose_account",
                        "label": "Elegir subcuenta"
                      },
                      "attempt": {
                        "step": "choose_account",
                        "handoff": {
                          "id": "0f2f6b0a-9a2c-4f4e-9a1e-6f7f2a5c3b21",
                          "expires_at": "2026-09-27T15:45:00.000Z"
                        },
                        "candidates": [
                          {
                            "external_id": "loc_8XkP2qR5tV",
                            "name": "Agencia Ejemplo — Ventas"
                          },
                          {
                            "external_id": "loc_3MnB7wQ1yZ",
                            "name": "Agencia Ejemplo — Soporte"
                          }
                        ],
                        "selection_expires_at": "2026-09-27T16:00:00.000Z",
                        "prompt": "Elige la subcuenta de GoHighLevel que quieres conectar."
                      },
                      "connected_at": null,
                      "last_synced_at": null,
                      "updated_at": "2026-09-27T15:02:00.000Z"
                    },
                    {
                      "provider": "mercadopago",
                      "display_name": "Mercado Pago",
                      "category": "payments",
                      "description": "Registra los pagos que recibes para saber cuántas ventas trae cada campaña.",
                      "state": "not_connected",
                      "state_label": "Sin conectar",
                      "account": null,
                      "last_account": null,
                      "reason": null,
                      "last_attempt": null,
                      "next_step": {
                        "action": "connect",
                        "label": "Conectar"
                      },
                      "attempt": null,
                      "connected_at": null,
                      "disconnected_at": null,
                      "last_synced_at": null,
                      "updated_at": null,
                      "connect_input": "none",
                      "connect_notice": null,
                      "catalog": null,
                      "addon_active": true
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}": {
      "get": {
        "summary": "Read one provider connection card",
        "description": "Everything the card renders, in one read: the state and its label, the connected account, the reason with a ready-to-render sentence, how the latest re-authorization ended (`last_attempt`), the next step with its button label, when it connected / stopped / last synced, and — only while a connect is in flight — the attempt. Never null: a workspace that never connected gets `state: \"not_connected\"`.\n\nA connection whose last sync failed stays `connected` with `reason.code: \"sync_error\"` (label «Problema de sincronización») and a `reconnect` next step; only a grant the provider no longer honours is `needs_reconnect`.\n\nWhile an account picker is open the candidates are re-read from the provider when older than five minutes; every other state is a database read only.\n\nRequires `integrations:read` (owner and admin).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel",
                "mercadopago",
                "shopify"
              ],
              "description": "The provider: `gohighlevel` (GoHighLevel), `mercadopago` (Mercado Pago), `shopify` (Shopify). Anything else is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The provider connection card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "display_name": "GoHighLevel",
                    "category": "crm",
                    "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                    "last_account": null,
                    "last_attempt": null,
                    "disconnected_at": null,
                    "connect_input": "none",
                    "connect_notice": null,
                    "catalog": null,
                    "addon_active": true,
                    "state": "connected",
                    "state_label": "Conectado",
                    "account": {
                      "external_id": "loc_8XkP2qR5tV",
                      "name": "Agencia Ejemplo"
                    },
                    "reason": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "attempt": null,
                    "connected_at": "2026-09-27T15:04:00.000Z",
                    "last_synced_at": "2026-09-27T15:20:00.000Z",
                    "updated_at": "2026-09-27T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/connect": {
      "post": {
        "summary": "Start (or resume) a provider connect",
        "description": "Starts the provider’s consent and answers the URL to send the WHOLE page to — straight to the provider’s own consent screen, which names this app. Every ending (connected, several accounts to choose from, cancelled, refused, expired) comes back to `return_to` with `connection_return=<provider>`, `connection_state`, and the provider’s `status`, `reason` and `handoff_id`; pass all of them to `POST …/return`, then strip them from the address bar.\n\n`return_to` must be an absolute URL on the app’s own origin (400 otherwise).\n\n**Idempotent.** A second click while a consent is live for the same `return_to` answers the SAME `url` and `handoff_id`. It works from every state: a first connect, a retry, a reconnect (`needs_reconnect`), a re-authorization of a connected account. Only a first connect moves the card to `connecting`; a reconnect keeps its state until the new consent lands.\n\n**Shopify** (`connect_input: \"shop\"`): send `shop`, the store’s address. 400 with `error.details.reason: \"invalid_shop\"` (and a Spanish message naming the fix) for a missing one or a custom storefront domain; 409 with `error.details.reason: \"other_store_connected\"` while a DIFFERENT store is connected (disconnect it first). Shopify’s consent screen names the connectivity partner’s app: show the card’s `connect_notice` before sending the page there. Once connected, the store’s catalog is imported and kept live (see `catalog` on the card and `POST …/sync`). A store needs no add-on.\n\nRequires `integrations:write` (owner and admin).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel",
                "mercadopago",
                "shopify"
              ],
              "description": "The provider: `gohighlevel` (GoHighLevel), `mercadopago` (Mercado Pago), `shopify` (Shopify). Anything else is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderConnectBody"
              },
              "example": {
                "return_to": "https://app.vitrinadev.com/w/agencia/settings/integrations"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The consent URL, its hand-off id and expiry, and the card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderConnectResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "url": "https://connect.example.com/gohighlevel/start?h=4d8f2a91c6b3",
                    "handoff_id": "0f2f6b0a-9a2c-4f4e-9a1e-6f7f2a5c3b21",
                    "expires_at": "2026-09-27T15:45:00.000Z",
                    "connection": {
                      "provider": "gohighlevel",
                      "display_name": "GoHighLevel",
                      "category": "crm",
                      "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                      "last_account": null,
                      "last_attempt": null,
                      "disconnected_at": null,
                      "connect_input": "none",
                      "connect_notice": null,
                      "catalog": null,
                      "addon_active": true,
                      "state": "connecting",
                      "state_label": "Conectando",
                      "account": null,
                      "reason": null,
                      "next_step": {
                        "action": "resume",
                        "label": "Continuar en GoHighLevel"
                      },
                      "attempt": {
                        "step": "consent",
                        "handoff": {
                          "id": "0f2f6b0a-9a2c-4f4e-9a1e-6f7f2a5c3b21",
                          "expires_at": "2026-09-27T15:45:00.000Z"
                        },
                        "candidates": null,
                        "selection_expires_at": null,
                        "prompt": "Termina la autorización en GoHighLevel para conectar."
                      },
                      "connected_at": null,
                      "last_synced_at": null,
                      "updated_at": "2026-09-27T15:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/return": {
      "post": {
        "summary": "Settle a provider connect after the admin comes back",
        "description": "Settles the connect on the provider’s own record of the consent — the `status` and `reason` from the return URL are hints only (a URL can be typed):\n\n* connected → `state: \"connected\"` with the account read back from the provider;\n* several accounts (GoHighLevel) → `attempt.step: \"choose_account\"` with `attempt.candidates`;\n* cancelled / refused / expired → the attempt ends with a SPECIFIC `reason` (`user_cancelled`, `account_connected_elsewhere`, `no_candidates`, `provider_denied`, …), never folded into one generic failure. On a card that was already connected the state is unchanged and the outcome is `last_attempt`;\n* not finished yet → the card comes back unchanged. If the return URL says the attempt failed with the retryable `internal_error`, the reason is recorded and the SAME consent link stays live for a retry; any other failure ends the attempt.\n\nEvery field is optional: a stripped query string still settles the workspace’s own pending consent (there is at most one). **Idempotent:** re-posting for a consent already settled answers the card as it stands. A consent finished in an OLDER tab after a newer connect answers the connected card.\n\n409 `handoff_mismatch` when the `handoff_id`/`state` presented is neither this card’s consent nor its last one and nothing is connected. 502 when the provider could not be reached — nothing is settled; retry.\n\nRequires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel",
                "mercadopago",
                "shopify"
              ],
              "description": "The provider: `gohighlevel` (GoHighLevel), `mercadopago` (Mercado Pago), `shopify` (Shopify). Anything else is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderReturnBody"
              },
              "example": {
                "handoff_id": "0f2f6b0a-9a2c-4f4e-9a1e-6f7f2a5c3b21",
                "state": "9f3a2d7c8b1e4a2f",
                "status": "success"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settled card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "display_name": "GoHighLevel",
                    "category": "crm",
                    "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                    "last_account": null,
                    "last_attempt": null,
                    "disconnected_at": null,
                    "connect_input": "none",
                    "connect_notice": null,
                    "catalog": null,
                    "addon_active": true,
                    "state": "connected",
                    "state_label": "Conectado",
                    "account": {
                      "external_id": "loc_8XkP2qR5tV",
                      "name": "Agencia Ejemplo"
                    },
                    "reason": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "attempt": null,
                    "connected_at": "2026-09-27T15:04:00.000Z",
                    "last_synced_at": "2026-09-27T15:20:00.000Z",
                    "updated_at": "2026-09-27T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`handoff_mismatch` — a stale tab finishing another consent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "handoff_mismatch",
                    "message": "Esta conexión ya no está esperando ese enlace. Actualiza la página y vuelve a conectar."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/finalize": {
      "post": {
        "summary": "Choose the account to connect from an open picker",
        "description": "Completes an open account picker (`attempt.step: \"choose_account\"`) with one `attempt.candidates[].external_id`. Served only for providers whose consent can end in a choice (GoHighLevel sub-accounts); any other provider is 404. **Idempotent:** the same account again on a card it already connected answers the card.\n\n* 409 `handoff_mismatch` — `handoff_id` was sent and is not the consent this card is waiting on (a stale tab); refresh the card;\n* 409 `not_selecting` — no choice is owed (refresh the card);\n* 409 `account_connected_elsewhere` — that account lives on another Vitrina workspace; the picker STAYS open, choose another;\n* 409 `selection_conflict` — another account was already chosen on this consent; connect again to change it;\n* 410 `selection_expired` — the picker’s 60-minute window ended; connect again;\n* 422 `candidate_unavailable` — the account is no longer reachable; the next card read refreshes the list.\n\nRequires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel"
              ],
              "description": "A provider whose consent can end in an account choice: `gohighlevel`. Any other provider is 404 — it never opens a picker.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderFinalizeBody"
              },
              "example": {
                "external_id": "loc_8XkP2qR5tV",
                "handoff_id": "0f2f6b0a-9a2c-4f4e-9a1e-6f7f2a5c3b21"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connected card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "display_name": "GoHighLevel",
                    "category": "crm",
                    "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                    "last_account": null,
                    "last_attempt": null,
                    "disconnected_at": null,
                    "connect_input": "none",
                    "connect_notice": null,
                    "catalog": null,
                    "addon_active": true,
                    "state": "connected",
                    "state_label": "Conectado",
                    "account": {
                      "external_id": "loc_8XkP2qR5tV",
                      "name": "Agencia Ejemplo"
                    },
                    "reason": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "attempt": null,
                    "connected_at": "2026-09-27T15:04:00.000Z",
                    "last_synced_at": "2026-09-27T15:20:00.000Z",
                    "updated_at": "2026-09-27T15:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`handoff_mismatch` (a stale tab), `not_selecting` (no choice owed), `account_connected_elsewhere` (the picker stays open) or `selection_conflict` (connect again).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "not_selecting",
                    "message": "Esta conexión no está esperando que elijas una subcuenta. Actualiza la página."
                  }
                }
              }
            }
          },
          "410": {
            "description": "`selection_expired` — the picker’s 60-minute window ended; connect again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "selection_expired",
                    "message": "Pasó demasiado tiempo para elegir la subcuenta. Vuelve a conectar GoHighLevel."
                  }
                }
              }
            }
          },
          "422": {
            "description": "`candidate_unavailable` — the account is no longer reachable; the next card read refreshes the list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "candidate_unavailable",
                    "message": "Esa subcuenta ya no está disponible. Elige otra de la lista."
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/sync": {
      "post": {
        "summary": "Sync a connected store’s catalog now",
        "description": "«Sincronizar ahora» on a store card: enqueues a walk of the whole store — every product, variant and stock level, read live from the store — then switches off the products the store no longer lists. The same walk runs hourly and right after a connect; stock changes reach Vitrina within seconds on their own, without this call. The card’s `catalog` reports the outcome.\n\nA walk that would switch off many products at once is held back (`catalog.last_sync_status: \"blocked\"`); `force: true` applies it, for a merchant who really emptied the store.\n\n400 when the store is not connected; 404 for a provider that is not a store. Requires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "shopify"
              ],
              "description": "A store (`category: commerce`): `shopify`. Any other provider is 404.",
              "example": "shopify"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProviderSyncBody"
              },
              "example": {}
            }
          }
        },
        "responses": {
          "202": {
            "description": "The walk is enqueued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderSyncResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enqueued": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/disconnect": {
      "post": {
        "summary": "Disconnect a provider",
        "description": "Disconnects the account at the provider and settles the card `disconnected` with `reason.code: \"disconnected_by_user\"`, keeping `last_account` so the card can say what was disconnected. A connect in flight with nothing connected yet is cancelled locally. **Idempotent:** a card with nothing live answers as it stands.\n\n409 `sync_in_flight` — the provider is mid-sync; nothing changed, try again in a few minutes. 502 `disconnect_refused` — the provider refused; nothing changed. 502 `UPSTREAM_ERROR` — the provider could not be reached; nothing changed.\n\nRequires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel",
                "mercadopago",
                "shopify"
              ],
              "description": "The provider: `gohighlevel` (GoHighLevel), `mercadopago` (Mercado Pago), `shopify` (Shopify). Anything else is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The disconnected card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProviderConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "display_name": "GoHighLevel",
                    "category": "crm",
                    "description": "Trae los formularios y las oportunidades de tu CRM para seguir cada lead hasta la venta.",
                    "last_account": {
                      "external_id": "loc_8XkP2qR5tV",
                      "name": "Agencia Ejemplo"
                    },
                    "last_attempt": null,
                    "disconnected_at": "2026-09-28T10:00:00.000Z",
                    "connect_input": "none",
                    "connect_notice": null,
                    "catalog": null,
                    "addon_active": true,
                    "state": "disconnected",
                    "state_label": "Desconectado",
                    "account": null,
                    "reason": {
                      "code": "disconnected_by_user",
                      "label": "Desconectaste GoHighLevel.",
                      "retryable": true
                    },
                    "next_step": {
                      "action": "connect",
                      "label": "Conectar"
                    },
                    "attempt": null,
                    "connected_at": null,
                    "last_synced_at": "2026-09-27T15:20:00.000Z",
                    "updated_at": "2026-09-28T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/stages": {
      "get": {
        "summary": "Read a CRM’s pipeline stages and what each one counts as",
        "description": "Everything the «Etapas de tu CRM» sheet renders, in one read: every pipeline of the connected CRM in CRM order, each stage with the outcome it counts as (`outcome`, or `null` for nothing), how that came to be (`mark`: `suggested` / `changed` / `default` / `unset`), the suggestion its name points to (or why there is none), whether it can be edited now, and the outcome list for the picker in its order, ending in `ignored` («No contar»).\n\nAfter the CRM connects, every stage nobody chose is set to its suggestion automatically; a stage someone chose is never touched, and a stage with no suggestion is left for a person (`needs_choice`, counted in `summary.needs_choice`).\n\nRead-only and cheap: it reads the stored copy of the CRM’s pipelines, never the CRM live. `status` other than `ready` comes with `status_label` and a `next_step`. A CRM that is not connected answers `status: \"not_connected\"` with no pipelines.\n\n`provider`: `gohighlevel`; any other provider is 404.\n\nRequires `integrations:read` (owner and admin).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel"
              ],
              "description": "A CRM whose pipeline stages map to outcomes. Any other provider is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The CRM’s stages and their outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CrmStageMapping"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "status": "ready",
                    "status_label": null,
                    "synced_at": "2026-09-27T10:00:00.000Z",
                    "syncing": false,
                    "can_write": true,
                    "write_blocked": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "outcomes": [
                      {
                        "key": "lead_created",
                        "label": "Lead nuevo"
                      },
                      {
                        "key": "contacted",
                        "label": "Contactado"
                      },
                      {
                        "key": "follow_up",
                        "label": "En seguimiento"
                      },
                      {
                        "key": "appointment_booked",
                        "label": "Cita agendada"
                      },
                      {
                        "key": "no_show",
                        "label": "No asistió"
                      },
                      {
                        "key": "showed",
                        "label": "Asistió"
                      },
                      {
                        "key": "qualified",
                        "label": "Calificado"
                      },
                      {
                        "key": "disqualified",
                        "label": "Descalificado"
                      },
                      {
                        "key": "closed_lost",
                        "label": "Venta perdida"
                      },
                      {
                        "key": "closed_won",
                        "label": "Venta ganada"
                      },
                      {
                        "key": "ignored",
                        "label": "No contar"
                      }
                    ],
                    "pipelines": [
                      {
                        "external_id": "pipeline_1",
                        "name": "Ventas",
                        "account_name": null,
                        "stages": [
                          {
                            "external_id": "stage_1",
                            "name": "Nuevo lead",
                            "position": 0,
                            "outcome": "lead_created",
                            "outcome_label": "Lead nuevo",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "lead_created",
                              "label": "Lead nuevo"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_2",
                            "name": "Agendado",
                            "position": 1,
                            "outcome": "appointment_booked",
                            "outcome_label": "Cita agendada",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "appointment_booked",
                              "label": "Cita agendada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_3",
                            "name": "Abono",
                            "position": 2,
                            "outcome": null,
                            "outcome_label": null,
                            "mark": "unset",
                            "suggested": null,
                            "no_suggestion_label": "El nombre de la etapa no indica un resultado. Elige uno.",
                            "needs_choice": true,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_4",
                            "name": "Venta cerrada",
                            "position": 3,
                            "outcome": "qualified",
                            "outcome_label": "Calificado",
                            "mark": "changed",
                            "suggested": {
                              "outcome": "closed_won",
                              "label": "Venta ganada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          }
                        ]
                      }
                    ],
                    "summary": {
                      "pipelines": 1,
                      "stages": 4,
                      "suggested": 2,
                      "changed": 1,
                      "needs_choice": 1,
                      "awaiting_sync": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/stages/sync": {
      "post": {
        "summary": "Re-read the CRM’s pipelines",
        "description": "Asks for a fresh read of the CRM’s pipelines — for a stage added in the CRM since the last read — and answers the stages as they stand (`syncing: true`). The read lands within minutes; new stages then get their suggestion applied automatically. Safe to repeat: a read already running is the same answer.\n\n409 `not_connected` — the CRM is not connected.\n\nRequires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel"
              ],
              "description": "A CRM whose pipeline stages map to outcomes. Any other provider is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The stages, with a read under way",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CrmStageMapping"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "status": "ready",
                    "status_label": null,
                    "synced_at": "2026-09-27T10:00:00.000Z",
                    "syncing": true,
                    "can_write": true,
                    "write_blocked": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "outcomes": [
                      {
                        "key": "lead_created",
                        "label": "Lead nuevo"
                      },
                      {
                        "key": "contacted",
                        "label": "Contactado"
                      },
                      {
                        "key": "follow_up",
                        "label": "En seguimiento"
                      },
                      {
                        "key": "appointment_booked",
                        "label": "Cita agendada"
                      },
                      {
                        "key": "no_show",
                        "label": "No asistió"
                      },
                      {
                        "key": "showed",
                        "label": "Asistió"
                      },
                      {
                        "key": "qualified",
                        "label": "Calificado"
                      },
                      {
                        "key": "disqualified",
                        "label": "Descalificado"
                      },
                      {
                        "key": "closed_lost",
                        "label": "Venta perdida"
                      },
                      {
                        "key": "closed_won",
                        "label": "Venta ganada"
                      },
                      {
                        "key": "ignored",
                        "label": "No contar"
                      }
                    ],
                    "pipelines": [
                      {
                        "external_id": "pipeline_1",
                        "name": "Ventas",
                        "account_name": null,
                        "stages": [
                          {
                            "external_id": "stage_1",
                            "name": "Nuevo lead",
                            "position": 0,
                            "outcome": "lead_created",
                            "outcome_label": "Lead nuevo",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "lead_created",
                              "label": "Lead nuevo"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_2",
                            "name": "Agendado",
                            "position": 1,
                            "outcome": "appointment_booked",
                            "outcome_label": "Cita agendada",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "appointment_booked",
                              "label": "Cita agendada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_3",
                            "name": "Abono",
                            "position": 2,
                            "outcome": null,
                            "outcome_label": null,
                            "mark": "unset",
                            "suggested": null,
                            "no_suggestion_label": "El nombre de la etapa no indica un resultado. Elige uno.",
                            "needs_choice": true,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_4",
                            "name": "Venta cerrada",
                            "position": 3,
                            "outcome": "qualified",
                            "outcome_label": "Calificado",
                            "mark": "changed",
                            "suggested": {
                              "outcome": "closed_won",
                              "label": "Venta ganada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          }
                        ]
                      }
                    ],
                    "summary": {
                      "pipelines": 1,
                      "stages": 4,
                      "suggested": 2,
                      "changed": 1,
                      "needs_choice": 1,
                      "awaiting_sync": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/integrations/connections/{provider}/stages/{stageId}": {
      "patch": {
        "summary": "Set what one CRM stage counts as",
        "description": "Sets one stage’s outcome — an `outcomes[].key`, or `ignored` for «No contar». Applies to what is measured from now on. **Idempotent:** the outcome already in force is answered without a write. Answers the whole sheet, updated (the stage reads `mark: \"suggested\"` when the choice equals the suggestion, `changed` otherwise).\n\n404 `stage_not_found` — the stage is no longer in the CRM. 409 `stage_not_writable` — a new stage still being read. 409 `stages_write_blocked` — the mapping cannot be edited now (`error.details.reason`, the same codes as `write_blocked.code`). 409 `not_connected`. 422 `invalid_outcome`. 402 without the Vitrina Ads add-on.\n\nRequires `integrations:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Integrations"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "gohighlevel"
              ],
              "description": "A CRM whose pipeline stages map to outcomes. Any other provider is 404.",
              "example": "gohighlevel"
            },
            "required": true,
            "name": "provider",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "description": "A `pipelines[].stages[].external_id` from the stages read.",
              "example": "stage_3"
            },
            "required": true,
            "name": "stageId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CrmStageOutcomeBody"
              },
              "example": {
                "outcome": "closed_won"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole sheet, with the stage updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CrmStageMapping"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "provider": "gohighlevel",
                    "status": "ready",
                    "status_label": null,
                    "synced_at": "2026-09-27T10:00:00.000Z",
                    "syncing": false,
                    "can_write": true,
                    "write_blocked": null,
                    "next_step": {
                      "action": "none",
                      "label": null
                    },
                    "outcomes": [
                      {
                        "key": "lead_created",
                        "label": "Lead nuevo"
                      },
                      {
                        "key": "contacted",
                        "label": "Contactado"
                      },
                      {
                        "key": "follow_up",
                        "label": "En seguimiento"
                      },
                      {
                        "key": "appointment_booked",
                        "label": "Cita agendada"
                      },
                      {
                        "key": "no_show",
                        "label": "No asistió"
                      },
                      {
                        "key": "showed",
                        "label": "Asistió"
                      },
                      {
                        "key": "qualified",
                        "label": "Calificado"
                      },
                      {
                        "key": "disqualified",
                        "label": "Descalificado"
                      },
                      {
                        "key": "closed_lost",
                        "label": "Venta perdida"
                      },
                      {
                        "key": "closed_won",
                        "label": "Venta ganada"
                      },
                      {
                        "key": "ignored",
                        "label": "No contar"
                      }
                    ],
                    "pipelines": [
                      {
                        "external_id": "pipeline_1",
                        "name": "Ventas",
                        "account_name": null,
                        "stages": [
                          {
                            "external_id": "stage_1",
                            "name": "Nuevo lead",
                            "position": 0,
                            "outcome": "lead_created",
                            "outcome_label": "Lead nuevo",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "lead_created",
                              "label": "Lead nuevo"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_2",
                            "name": "Agendado",
                            "position": 1,
                            "outcome": "appointment_booked",
                            "outcome_label": "Cita agendada",
                            "mark": "suggested",
                            "suggested": {
                              "outcome": "appointment_booked",
                              "label": "Cita agendada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_3",
                            "name": "Abono",
                            "position": 2,
                            "outcome": null,
                            "outcome_label": null,
                            "mark": "unset",
                            "suggested": null,
                            "no_suggestion_label": "El nombre de la etapa no indica un resultado. Elige uno.",
                            "needs_choice": true,
                            "writable": true,
                            "not_writable_label": null
                          },
                          {
                            "external_id": "stage_4",
                            "name": "Venta cerrada",
                            "position": 3,
                            "outcome": "qualified",
                            "outcome_label": "Calificado",
                            "mark": "changed",
                            "suggested": {
                              "outcome": "closed_won",
                              "label": "Venta ganada"
                            },
                            "no_suggestion_label": null,
                            "needs_choice": false,
                            "writable": true,
                            "not_writable_label": null
                          }
                        ]
                      }
                    ],
                    "summary": {
                      "pipelines": 1,
                      "stages": 4,
                      "suggested": 2,
                      "changed": 1,
                      "needs_choice": 1,
                      "awaiting_sync": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "The Vitrina Ads add-on is not active and nothing is connected (or in flight) for this provider. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`. A connection that outlived the add-on is never refused here: it stays readable, finishable and disconnectable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/report": {
      "get": {
        "summary": "Reporte: campaigns, ad sets or ads with their funnel, costs and cash return",
        "description": "One table at `level` (campaign, ad set or ad) for the window. Per row: the spend and the cash credited by the attribution engine, Vitrina’s own conversational funnel (conversations each ad opened → answered → citas → attended → closes) with the cost of each step, «Retorno» = cash ÷ spend (`null` when no payment was recorded in the period), and Meta’s own figures apart. `tail` holds the period’s conversations with no ad behind them, so from-ads + tail = the workspace total; `reconciliation` states Σ spend against `/ads/overview`. A failed read is `null`, never 0.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "campaign",
                "ad_set",
                "ad"
              ],
              "default": "campaign",
              "description": "Campaña, conjunto (ad set) or anuncio. Default `campaign`."
            },
            "required": false,
            "description": "Campaña, conjunto (ad set) or anuncio. Default `campaign`.",
            "name": "level",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The report at the requested level",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "level": "campaign",
                    "window": {
                      "from": "2026-08-30",
                      "to": "2026-09-28"
                    },
                    "rows": [
                      {
                        "external_id": "120211000000000001",
                        "level": "campaign",
                        "name": "Implantes · Septiembre",
                        "parent": null,
                        "thumbnail_url": null,
                        "spark": [
                          13200,
                          14100,
                          13900
                        ],
                        "spend": 412300,
                        "funnel": {
                          "conversations": 120,
                          "replied": 110,
                          "appointments": 20,
                          "attended": 12,
                          "closes": 2
                        },
                        "cost_per": {
                          "conversation": 3436,
                          "appointment": 20615,
                          "attended": 34358,
                          "close": 206150
                        },
                        "cash": 0,
                        "return": null,
                        "meta": {
                          "conversations_started": 131,
                          "cost_per_conversation": 3147
                        }
                      }
                    ],
                    "tail": [
                      {
                        "key": "direct",
                        "conversations": 210
                      },
                      {
                        "key": "website",
                        "conversations": 40
                      }
                    ],
                    "totals": {
                      "spend": 412300,
                      "funnel": {
                        "conversations": 120,
                        "replied": 110,
                        "appointments": 20,
                        "attended": 12,
                        "closes": 2
                      },
                      "cost_per": {
                        "conversation": 3436,
                        "appointment": 20615,
                        "attended": 34358,
                        "close": 206150
                      },
                      "cash": 0,
                      "return": null,
                      "meta": {
                        "conversations_started": 131,
                        "cost_per_conversation": 3147
                      }
                    },
                    "pipeline": {
                      "closed_count": 3,
                      "closed_value": 93082
                    },
                    "cash_recorded": false,
                    "reconciliation": {
                      "overview_spend": 412300,
                      "spend_matches": true,
                      "conversations_total": 370,
                      "conversations_from_ads": 120,
                      "conversations_without_ad": 250
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/report/export": {
      "get": {
        "summary": "Export the Reporte (aggregates only)",
        "description": "The same table as `GET /ads/report`, as a file. CSV: `;`, Spanish headers, `dd-mm-aaaa` dates, a preamble with the period, the model and «Retorno = caja». XLSX: the table plus a «Notas» sheet. No row names a person. Audited as `ads.report.export`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "campaign",
                "ad_set",
                "ad"
              ],
              "default": "campaign",
              "description": "Campaña, conjunto (ad set) or anuncio. Default `campaign`."
            },
            "required": false,
            "description": "Campaña, conjunto (ad set) or anuncio. Default `campaign`.",
            "name": "level",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "csv",
                "xlsx"
              ],
              "default": "xlsx",
              "description": "`csv`: `;`-separated, Spanish headers, `dd-mm-aaaa` dates, a preamble with the period, the model and «Retorno = caja». `xlsx`: the same table plus a «Notas» sheet. Aggregates only — no person rows."
            },
            "required": false,
            "description": "`csv`: `;`-separated, Spanish headers, `dd-mm-aaaa` dates, a preamble with the period, the model and «Retorno = caja». `xlsx`: the same table plus a «Notas» sheet. Aggregates only — no person rows.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              },
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/ads/{id}": {
      "get": {
        "summary": "The ad sheet: creative, funnel and the people it brought (masked)",
        "description": "One ad: the creative and delivery metrics, the SAME funnel/costs/cash as its Reporte row for the window, and the conversations it opened (a label, the furthest stage, its value). Never a phone or an e-mail; for healthcare the label is always initials. There is no export of people.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The ad sheet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAdSheet"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ad": {
                      "external_id": "120212000000000009",
                      "name": "Video testimonio 15s",
                      "ad_set": {
                        "external_id": "120213000000000004",
                        "name": "INTERESES"
                      },
                      "campaign": {
                        "external_id": "120211000000000001"
                      },
                      "thumbnail_url": null,
                      "title": "Implantes en un día",
                      "body": "Agenda tu evaluación.",
                      "has_video": true,
                      "video_external_id": "120214000000000009",
                      "ads_manager_url": "https://www.facebook.com/adsmanager/manage/ads?act=<account-id>&selected_ad_ids=120212000000000009",
                      "spend": 168000,
                      "impressions": 24100,
                      "clicks": 337,
                      "ctr": 1.4,
                      "cpm": 6971,
                      "cpc": 498,
                      "cash_count": 0,
                      "cash_value": 0,
                      "meta_conversations_started": 22
                    },
                    "window": {
                      "from": "2026-08-30",
                      "to": "2026-09-28"
                    },
                    "row": {
                      "spend": 412300,
                      "funnel": {
                        "conversations": 120,
                        "replied": 110,
                        "appointments": 20,
                        "attended": 12,
                        "closes": 2
                      },
                      "cost_per": {
                        "conversation": 3436,
                        "appointment": 20615,
                        "attended": 34358,
                        "close": 206150
                      },
                      "cash": 0,
                      "return": null,
                      "meta": {
                        "conversations_started": 131,
                        "cost_per_conversation": 3147
                      }
                    },
                    "cash_recorded": false,
                    "people": [
                      {
                        "conversation_id": "00000000-0000-4000-8000-00000000c0a1",
                        "contact_id": "00000000-0000-4000-8000-00000000c0a2",
                        "label": "M. R.",
                        "masked": true,
                        "stage": "closed_won",
                        "stage_at": "2026-09-25T15:00:00.000Z",
                        "opened_at": "2026-09-20T13:10:00.000Z",
                        "value": 1250000
                      }
                    ],
                    "people_more": false,
                    "people_hidden": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/ads/{id}/video": {
      "get": {
        "summary": "A playable source for the ad’s video",
        "description": "Resolves the ad’s video for playing in the product. `type: source` is a direct URL for a `<video>`; `type: embed` is the Facebook embed iframe plus the public watch link (common — not an error). Meta signs the URL and it expires within minutes: it is answered with `Cache-Control: no-store`, so ask when the person presses play, never ahead of time. 404 when the ad has no video (or is not yours).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The video source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAdVideo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "type": "source",
                    "source": "https://<video-cdn-host>/<signed-path>",
                    "embed_url": null,
                    "watch_url": "https://www.facebook.com/watch/?v=120214000000000009"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/paid-funnel": {
      "get": {
        "summary": "Paid funnel: spend, leads, qualified, booked, held and paid per arm, campaign, ad set and ad",
        "description": "The scoreboard that judges a paid test on cost per demo HELD. Per row: people at each step (lead → ICP-qualified → demo booked → demo held → first paid invoice), the cost of each step, contacted within 24 h, lead-to-booked, show rate and qualified share, for one destination `arm` (`whatsapp`, `instant_form`, `website`, `other`; an ad with spend and nobody is `unassigned`). Steps are read from the outcome ledger the Meta events leave from, so a column equals the events sent for the same period (`basis=event`; `reconciliation.events` prints them). People with no id at the level sit on the arm’s `unattributed` row, so rows sum to `totals`; `reconciliation.spend_matches` checks Σ spend against `/ads/overview`. A failed read is `null`, never 0.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD, Santiago."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD, Santiago.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD, Santiago."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD, Santiago.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "arm",
                "campaign",
                "ad_set",
                "ad"
              ],
              "default": "arm",
              "description": "`arm` (WhatsApp / Instant Form / website), `campaign`, `ad_set` or `ad`. `arms` is always returned whatever the level."
            },
            "required": false,
            "description": "`arm` (WhatsApp / Instant Form / website), `campaign`, `ad_set` or `ad`. `arms` is always returned whatever the level.",
            "name": "level",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "cohort",
                "event"
              ],
              "default": "cohort",
              "description": "`cohort` (default): the people whose lead fell in the window, every later step counted whenever it happened. `event`: every column is the number of people whose event of that step fell in the window, so each equals the events sent to Meta for the same period."
            },
            "required": false,
            "description": "`cohort` (default): the people whose lead fell in the window, every later step counted whenever it happened. `event`: every column is the number of people whose event of that step fell in the window, so each equals the events sent to Meta for the same period.",
            "name": "basis",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model for the spend read. Default `last_touch`."
            },
            "required": false,
            "description": "The attribution model for the spend read. Default `last_touch`.",
            "name": "model",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The funnel at the requested level",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsDemoFunnel"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "level": "arm",
                    "basis": "cohort",
                    "window": {
                      "from": "2026-11-16",
                      "to": "2026-11-29"
                    },
                    "rows": [
                      {
                        "arm": "whatsapp",
                        "external_id": null,
                        "name": null,
                        "parent": null,
                        "unattributed": false,
                        "leads": 100,
                        "qualified": 50,
                        "booked": 25,
                        "held": 12,
                        "paid": 0,
                        "contacted_24h": 90,
                        "spend": 1400000,
                        "costs": {
                          "per_lead": 14000,
                          "per_qualified": 28000,
                          "per_booked": 56000,
                          "per_held": 112000,
                          "per_paid": null
                        },
                        "rates": {
                          "qualified_share": 0.5,
                          "contacted_24h_rate": 0.9,
                          "lead_to_booked": 0.25,
                          "show_rate": 0.5
                        }
                      }
                    ],
                    "arms": [],
                    "totals": {
                      "leads": 100,
                      "qualified": 50,
                      "booked": 25,
                      "held": 12,
                      "paid": 0,
                      "contacted_24h": 90,
                      "spend": 1400000,
                      "costs": {
                        "per_lead": 14000,
                        "per_qualified": 28000,
                        "per_booked": 56000,
                        "per_held": 112000,
                        "per_paid": null
                      },
                      "rates": {
                        "qualified_share": 0.5,
                        "contacted_24h_rate": 0.9,
                        "lead_to_booked": 0.25,
                        "show_rate": 0.5
                      }
                    },
                    "reconciliation": {
                      "overview_spend": 1400000,
                      "spend_matches": true,
                      "events": null,
                      "counts_match": null,
                      "leads_without_ad": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/overview": {
      "get": {
        "summary": "Ad spend, attributed revenue, ROAS and outcomes for a window",
        "description": "The attribution engine’s overview: `current` and `previous` (the immediately-preceding window of equal length), each carrying spend, clicks, ROAS, the cash/first-payment/recurring/refund/dispute legs and per-conversion-type outcome counts.\n\n`spend`, `roas` and `clicks` are `null` — never `0` — exactly when `spend_available` is `false` (#1809): the engine could not scope spend to the window/model requested. A measured zero still arrives as `0` with `spend_available: true`. Render `null` as \"—\", never as zero, and let every ratio you derive from it inherit the null.\n\nEvery other money field (`revenue`, `cash_revenue`, `first_payment_revenue`, …) is parsed from the engine’s exact-decimal wire string (ADR 0018) to a number ONCE, at the provider mapper.\n\n`meta_send` is the experiment marker (the same as `/ads/meta-send`): when the workspace started sending its outcomes to Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "total",
                "day"
              ],
              "description": "`day` ALSO answers `days[]` and `previous_days[]` — one point per calendar day of the window and of the previous window (the engine’s own day rows, never recomputed), beside the unchanged `current`/`previous` totals. Default `total`."
            },
            "required": false,
            "description": "`day` ALSO answers `days[]` and `previous_days[]` — one point per calendar day of the window and of the previous window (the engine’s own day rows, never recomputed), beside the unchanged `current`/`previous` totals. Default `total`.",
            "name": "grain",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "previous_period"
              ],
              "description": "Documentary only — `/ads/overview` always returns `previous` (the immediately-preceding window of equal length) alongside `current`; there is nothing to switch on."
            },
            "required": false,
            "description": "Documentary only — `/ads/overview` always returns `previous` (the immediately-preceding window of equal length) alongside `current`; there is nothing to switch on.",
            "name": "compare",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s overview, current and previous",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsOverview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "current": {
                      "spend": 845200,
                      "spend_available": true,
                      "clicks": 3120,
                      "revenue": 3120000,
                      "organic_direct_revenue": 410000,
                      "roas": 3.69,
                      "outcomes": 42,
                      "attributed_outcomes": 31,
                      "attributed_cash_outcomes": 9,
                      "coverage_percent": 73.8,
                      "cash_revenue": 2700000,
                      "cash_payments": 11,
                      "first_payment_revenue": 1950000,
                      "first_payment_customers": 8,
                      "recurring_revenue": 750000,
                      "recurring_customers": 3,
                      "refunds": 0,
                      "refund_amount": 0,
                      "disputes": 0,
                      "dispute_amount": 0,
                      "dispute_reversed_amount": 0,
                      "outcome_counts": {
                        "lead_created": 30,
                        "appointment_booked": 9,
                        "closed_won": 3
                      }
                    },
                    "previous": {
                      "spend": null,
                      "spend_available": false,
                      "clicks": null,
                      "revenue": 2480000,
                      "organic_direct_revenue": 410000,
                      "roas": null,
                      "outcomes": 42,
                      "attributed_outcomes": 31,
                      "attributed_cash_outcomes": 9,
                      "coverage_percent": 73.8,
                      "cash_revenue": 2700000,
                      "cash_payments": 11,
                      "first_payment_revenue": 1950000,
                      "first_payment_customers": 8,
                      "recurring_revenue": 750000,
                      "recurring_customers": 3,
                      "refunds": 0,
                      "refund_amount": 0,
                      "disputes": 0,
                      "dispute_amount": 0,
                      "dispute_reversed_amount": 0,
                      "outcome_counts": {
                        "lead_created": 30,
                        "appointment_booked": 9,
                        "closed_won": 3
                      }
                    },
                    "days": [
                      {
                        "date": "2026-08-24",
                        "spend": 31800,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-25",
                        "spend": 35700,
                        "revenue": 130000,
                        "outcomes": 3,
                        "attributed_outcomes": 2,
                        "partial": false
                      },
                      {
                        "date": "2026-08-26",
                        "spend": 28700,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-27",
                        "spend": 33400,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-28",
                        "spend": 30300,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-29",
                        "spend": 19700,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-30",
                        "spend": 22100,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-31",
                        "spend": 28700,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-01",
                        "spend": 33400,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-02",
                        "spend": 30300,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-03",
                        "spend": 31800,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-04",
                        "spend": 35700,
                        "revenue": 130000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-05",
                        "spend": 17800,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-06",
                        "spend": 20700,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-07",
                        "spend": 30300,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-08",
                        "spend": 31800,
                        "revenue": 120000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-09",
                        "spend": 35700,
                        "revenue": 130000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-10",
                        "spend": 28700,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-11",
                        "spend": 33400,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-12",
                        "spend": 18800,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-13",
                        "spend": 19700,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-14",
                        "spend": 35700,
                        "revenue": 130000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-15",
                        "spend": 28700,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-16",
                        "spend": 33400,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-17",
                        "spend": 30200,
                        "revenue": 110000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-18",
                        "spend": 31800,
                        "revenue": 120000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-19",
                        "spend": 22100,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-20",
                        "spend": 17800,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-21",
                        "spend": 33400,
                        "revenue": 120000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-09-22",
                        "spend": 13600,
                        "revenue": 50000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": true
                      }
                    ],
                    "previous_days": [
                      {
                        "date": "2026-07-25",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-26",
                        "spend": null,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-27",
                        "spend": null,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-28",
                        "spend": null,
                        "revenue": 100000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-29",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-30",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-07-31",
                        "spend": null,
                        "revenue": 110000,
                        "outcomes": 3,
                        "attributed_outcomes": 2,
                        "partial": false
                      },
                      {
                        "date": "2026-08-01",
                        "spend": null,
                        "revenue": 50000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-02",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-03",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-04",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-05",
                        "spend": null,
                        "revenue": 110000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-06",
                        "spend": null,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-07",
                        "spend": null,
                        "revenue": 100000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-08",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-09",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-10",
                        "spend": null,
                        "revenue": 110000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-11",
                        "spend": null,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-12",
                        "spend": null,
                        "revenue": 100000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-13",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-14",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-15",
                        "spend": null,
                        "revenue": 70000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-16",
                        "spend": null,
                        "revenue": 50000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-17",
                        "spend": null,
                        "revenue": 100000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-18",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-19",
                        "spend": null,
                        "revenue": 90000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-20",
                        "spend": null,
                        "revenue": 110000,
                        "outcomes": 2,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-21",
                        "spend": null,
                        "revenue": 80000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-22",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      },
                      {
                        "date": "2026-08-23",
                        "spend": null,
                        "revenue": 60000,
                        "outcomes": 1,
                        "attributed_outcomes": 1,
                        "partial": false
                      }
                    ],
                    "gaps": [
                      {
                        "from": "2026-09-12",
                        "to": "2026-09-15",
                        "cause": "meta_disconnected",
                        "metrics": [
                          "spend"
                        ],
                        "incident_id": "b1b1b1b1-0000-4000-8000-000000000002"
                      }
                    ],
                    "meta_send": {
                      "first_started_at": "2026-10-01T13:00:00.000Z",
                      "sending": true,
                      "intervals": [
                        {
                          "started_at": "2026-10-01T13:00:00.000Z",
                          "stopped_at": "2026-10-20T15:30:00.000Z"
                        },
                        {
                          "started_at": "2026-11-01T12:00:00.000Z",
                          "stopped_at": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/measurement": {
      "get": {
        "summary": "Measurement incidents: what stopped measuring, since when",
        "description": "The measurement watchdog’s ledger (W1.1, «Vigilancia de la medición»). `open` are the legs broken right now — the Meta ad account read (disconnected or stale), the site tag’s heartbeat, the Conversions API sends, a WhatsApp channel, clinic facts not reaching the attribution — each with the day the data stopped and its ONE fix (`action.path`). `recent` are the ones resolved in the last 14 days, with who fixed them.\n\nThe owner and admins are told by WhatsApp and email at +0 h, +3 d and +7 d while an incident stays open. `/ads/overview` and `/ads/campaigns` carry the same incidents as `gaps` over their window.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Open and recently resolved incidents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMeasurement"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "open": [
                      {
                        "id": "b1b1b1b1-0000-4000-8000-000000000001",
                        "cause": "tag_silent",
                        "label": "Etiqueta del sitio sin señal",
                        "sentence": "La etiqueta de tu sitio web no envía visitas desde el 20 sep",
                        "action": {
                          "label": "Revisar la etiqueta",
                          "path": "/anuncios/activar?paso=tag"
                        },
                        "since": "2026-09-20T14:05:00.000Z",
                        "opened_at": "2026-09-27T15:00:00.000Z",
                        "resolved_at": null,
                        "resolution": null,
                        "resolved_by_name": null,
                        "metrics": []
                      }
                    ],
                    "recent": [
                      {
                        "id": "b1b1b1b1-0000-4000-8000-000000000002",
                        "cause": "meta_disconnected",
                        "label": "Meta Ads desconectado",
                        "sentence": "Tus anuncios de Meta dejaron de medirse el 12 sep",
                        "action": {
                          "label": "Reconectar Meta",
                          "path": "/anuncios/activar?paso=meta"
                        },
                        "since": "2026-09-12T11:20:00.000Z",
                        "opened_at": "2026-09-12T12:00:00.000Z",
                        "resolved_at": "2026-09-15T13:42:00.000Z",
                        "resolution": "reconnected",
                        "resolved_by_name": "Camila",
                        "metrics": [
                          "spend"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/meta-send": {
      "get": {
        "summary": "When the workspace started sending its outcomes to Meta",
        "description": "The experiment marker of «Antes y después de enviar a Meta». `first_started_at` is the first time outcomes started shipping to Meta (the owner’s «Empezar a enviar»): before it, Meta optimised the ads with no events from Vitrina. `intervals` is the full on/off history (sending can stop — «Solo medir», an undo — and start again), oldest first; `stopped_at: null` is the open one.\n\nRecorded when a send action or a direct conversion-sync write changes what ships to Meta, and reconciled daily for changes made outside Vitrina (dated when Vitrina saw them). Dates only — no person, event or message. A fact about the workspace: the sample answers the real one. `/ads/overview` carries the same object as `meta_send`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The experiment marker and its intervals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMetaSendMarker"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "first_started_at": "2026-10-01T13:00:00.000Z",
                    "sending": true,
                    "intervals": [
                      {
                        "started_at": "2026-10-01T13:00:00.000Z",
                        "stopped_at": "2026-10-20T15:30:00.000Z"
                      },
                      {
                        "started_at": "2026-11-01T12:00:00.000Z",
                        "stopped_at": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/campaigns": {
      "get": {
        "summary": "Campaign performance for a window",
        "description": "The attribution engine’s campaign ranking (the ranked top-N shape — always the campaign rollup, never the per-ad-set/per-ad ranking). One row per campaign: spend, attributed outcome count/value and ROAS, plus the direct/inherited cash split when the engine can compute it.\n\n`campaign_external_id` on each row is the PLATFORM id — pass it as `campaign_id` to `/ads/attributed-sales` for the drill-down.\n\n`appointments_booked` / `cost_per_appointment` (spend ÷ citas, over `metric_windows.appointments`) and `paying_patients` / `cost_per_paying_patient` (spend ÷ distinct ad patients who paid, over `metric_windows.paying_patients`) are per row, `null` (never 0) with no spend or no cita / payment; `totals` is the footer (its `paying_patients` is the account-level figure of `/ads/overview`).\n\n`best_campaign` is THE «best campaign» for every screen: the highest total return of its ad patients (first payment + cuotas ÷ spend) among campaigns with spend and at least 3 payments in the covered window, ties to the one with more paying patients. `rule` states the metric, threshold and window; `reason` says why none is named. Display it as is; never rank the rows again.\n\n`closed_outcome_count` is the period’s closes credited to the campaign (recorded at the closed stage, matched to an ad) — a period-true count that does not depend on how much of `/ads/feed` was loaded; `null` while that credit is unknown.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` adds each row’s `series[]`: one `{date, spend, outcome_count}` per day of the window (the last 92 days at most)."
            },
            "required": false,
            "description": "`1` adds each row’s `series[]`: one `{date, spend, outcome_count}` per day of the window (the last 92 days at most).",
            "name": "series",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s campaigns, ranked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsCampaigns"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "campaign_id": "a7a7a7a7-0000-4000-8000-000000000001",
                        "campaign_name": "Leads · Septiembre",
                        "campaign_external_id": "120211000000000001",
                        "spend": 845200,
                        "outcome_count": 31,
                        "outcome_value": 3120000,
                        "roas": 3.69,
                        "direct_cash_value": 2100000,
                        "inherited_cash_value": 600000,
                        "closed_outcome_count": 9,
                        "top_ad": {
                          "external_id": "120212000000000009",
                          "name": "Video testimonio 15s",
                          "creative_thumbnail_url": "https://cdn.vitrinadev.com/examples/ads/video-testimonio-15s.jpg",
                          "video_external_id": "120212000000000019",
                          "ads_manager_url": "https://business.facebook.com/adsmanager/manage/ads?act=1234567890&selected_ad_ids=120212000000000009"
                        },
                        "series": [
                          {
                            "date": "2026-08-24",
                            "spend": 31800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-25",
                            "spend": 35700,
                            "outcome_count": 2
                          },
                          {
                            "date": "2026-08-26",
                            "spend": 28700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-27",
                            "spend": 33400,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-28",
                            "spend": 30300,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-29",
                            "spend": 19700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-30",
                            "spend": 22100,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-08-31",
                            "spend": 28700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-01",
                            "spend": 33400,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-02",
                            "spend": 30300,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-03",
                            "spend": 31800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-04",
                            "spend": 35700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-05",
                            "spend": 17800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-06",
                            "spend": 20700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-07",
                            "spend": 30300,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-08",
                            "spend": 31800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-09",
                            "spend": 35700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-10",
                            "spend": 28700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-11",
                            "spend": 33400,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-12",
                            "spend": 18800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-13",
                            "spend": 19700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-14",
                            "spend": 35700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-15",
                            "spend": 28700,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-16",
                            "spend": 33400,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-17",
                            "spend": 30200,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-18",
                            "spend": 31800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-19",
                            "spend": 22100,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-20",
                            "spend": 17800,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-21",
                            "spend": 33400,
                            "outcome_count": 1
                          },
                          {
                            "date": "2026-09-22",
                            "spend": 13600,
                            "outcome_count": 1
                          }
                        ]
                      }
                    ],
                    "gaps": [
                      {
                        "from": "2026-09-12",
                        "to": "2026-09-15",
                        "cause": "meta_disconnected",
                        "metrics": [
                          "spend"
                        ],
                        "incident_id": "b1b1b1b1-0000-4000-8000-000000000002"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/attributed-sales": {
      "get": {
        "summary": "One campaign’s attributed conversions (the drill-down)",
        "description": "The attribution engine’s conversions for one campaign: every conversion credited to `campaign_id` under the window/model, each carrying its credited share (`credited_value`/`credited_weight`), its position in the touch path, and whether the credit is `is_inherited` (bridged lead→cash, never counted in ROAS).\n\n`customer_name`/`customer_email` are `null` unless the tenant’s delegated key also carries `customers:read` — every other field is visible under `campaigns:read` alone.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The campaign’s PLATFORM id — the `campaign_external_id` an `/ads/campaigns` row returns, never the engine’s internal `campaign_id`."
            },
            "required": true,
            "description": "The campaign’s PLATFORM id — the `campaign_external_id` an `/ads/campaigns` row returns, never the engine’s internal `campaign_id`.",
            "name": "campaign_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "An ad’s PLATFORM id inside `campaign_id`: keeps only the `people` an ad of that id opened a conversation with (Vitrina’s own record — the first message’s referral, or a historical click). The engine’s credit is campaign-grain, so `data` (the raw conversions) is never filtered by it."
            },
            "required": false,
            "description": "An ad’s PLATFORM id inside `campaign_id`: keeps only the `people` an ad of that id opened a conversation with (Vitrina’s own record — the first message’s referral, or a historical click). The engine’s credit is campaign-grain, so `data` (the raw conversions) is never filtered by it.",
            "name": "ad_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign’s attributed conversions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAttributedSales"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "conversion_id": "a8a8a8a8-0000-4000-8000-000000000001",
                        "event_type": "closed_won",
                        "event_time": "2026-09-12T16:40:00.000Z",
                        "customer_profile_id": "a9a9a9a9-0000-4000-8000-000000000001",
                        "customer_key": null,
                        "customer_name": "María José",
                        "customer_email": null,
                        "value_amount": 1200000,
                        "revenue_type": "accepted_value",
                        "credited_value": 1200000,
                        "credited_weight": 1,
                        "touch_count": 3,
                        "path_length": 3,
                        "first_touch_time": "2026-09-06T21:14:00.000Z",
                        "last_touch_time": "2026-09-11T13:02:00.000Z",
                        "is_inherited": false
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/people": {
      "get": {
        "summary": "Who is behind an Ads number (name, RUT, phone)",
        "description": "The people behind ONE count of the Ads screens, read from the very read that count is folded from — so `count` equals the number shown and `people` has exactly `count` rows (unless `truncated`). `metric` names the count: the patient return (`paid_patients`, `arrived_patients`, `returned_patients` — overall, per campaign, per ad, or «sin campaña conocida» with `unplaced=1`), the people funnel (`funnel_<step>` = `/ads/stages` M1 / M2 / M3) or the conversations ads opened (`conversations[_<fact>]`, one row per conversation).\n\nEach row carries the contact and its roster patient with exactly the shape and identity gate of `/ads/attributed-sales` (`patient_link`, `patient.{id,name,rut,phone}`: `name` / `rut` / `phone` need `contacts:read` + `clinic:read`, plus `clinic_patients:read` for an API credential), its ad / campaign of origin, the stage reached, its dates and — for the patient return — every counted payment. This is a DIFFERENT population from `/ads/attributed-sales` (the engine’s last-click credit of one campaign): open the list of the number you show.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "paid_patients",
                "arrived_patients",
                "returned_patients",
                "funnel_contacted",
                "funnel_booked",
                "funnel_attended",
                "funnel_quote_presented",
                "funnel_accepted",
                "funnel_paid",
                "new_people",
                "new_people_booked",
                "new_people_paid",
                "returned_people",
                "returned_people_booked",
                "conversations",
                "conversations_replied",
                "conversations_booked",
                "conversations_attended",
                "conversations_closed"
              ],
              "description": "WHICH count to list — each is read from the very read the count is folded from, so `count` equals the number the screen shows:\n· `paid_patients` / `arrived_patients` / `returned_patients` — `patient_return.patients` / `.arrived_patients` / `.returned_patients` of `/ads/overview` (no scope), of a `/ads/campaigns` row (`campaign_id`), of an ad (`ad_id`), or «Anuncios aún sin campaña conocida» (`unplaced=1`).\n· `funnel_<step>` — `/ads/funnel` `steps[<step>].ads` = `/ads/stages` M1 `m1.conversations` (`funnel_contacted`), M2 `m2.booked` / `m2.attended`, M3 `m3.quote_presented` / `m3.accepted` / `m3.paid`. No scope.\n· `new_people[_booked|_paid]`, `returned_people[_booked]` — `/ads/stages` `m2.new_people` (D93): `people` / `booked` / `paid` and `returned.people` / `returned.booked`, over that block’s own `window` (the read’s `window`). No scope.\n· `conversations[_replied|_booked|_attended|_closed]` — the conversations ads opened (one row per CONVERSATION), all ads, one campaign (`campaign_id`, through the ad → campaign map as `/ads/campaigns` counts them) or one ad (`ad_id`)."
            },
            "required": true,
            "description": "WHICH count to list — each is read from the very read the count is folded from, so `count` equals the number the screen shows:\n· `paid_patients` / `arrived_patients` / `returned_patients` — `patient_return.patients` / `.arrived_patients` / `.returned_patients` of `/ads/overview` (no scope), of a `/ads/campaigns` row (`campaign_id`), of an ad (`ad_id`), or «Anuncios aún sin campaña conocida» (`unplaced=1`).\n· `funnel_<step>` — `/ads/funnel` `steps[<step>].ads` = `/ads/stages` M1 `m1.conversations` (`funnel_contacted`), M2 `m2.booked` / `m2.attended`, M3 `m3.quote_presented` / `m3.accepted` / `m3.paid`. No scope.\n· `new_people[_booked|_paid]`, `returned_people[_booked]` — `/ads/stages` `m2.new_people` (D93): `people` / `booked` / `paid` and `returned.people` / `returned.booked`, over that block’s own `window` (the read’s `window`). No scope.\n· `conversations[_replied|_booked|_attended|_closed]` — the conversations ads opened (one row per CONVERSATION), all ads, one campaign (`campaign_id`, through the ad → campaign map as `/ads/campaigns` counts them) or one ad (`ad_id`).",
            "name": "metric",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "A campaign’s PLATFORM id (`/ads/campaigns` `campaign_external_id`). Patient-return and conversation metrics only."
            },
            "required": false,
            "description": "A campaign’s PLATFORM id (`/ads/campaigns` `campaign_external_id`). Patient-return and conversation metrics only.",
            "name": "campaign_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{1,32}$",
              "description": "An ad’s PLATFORM id. Patient-return and conversation metrics only."
            },
            "required": false,
            "description": "An ad’s PLATFORM id. Patient-return and conversation metrics only.",
            "name": "ad_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1`: the ad patients whose payments no `/ads/campaigns` row of the window holds (no known campaign, or a campaign with no row) — «Anuncios aún sin campaña conocida». Patient-return metrics only."
            },
            "required": false,
            "description": "`1`: the ad patients whose payments no `/ads/campaigns` row of the window holds (no known campaign, or a campaign with no row) — «Anuncios aún sin campaña conocida». Patient-return metrics only.",
            "name": "unplaced",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 5000,
              "description": "Rows served (default 1000, max 5000). `count` is always the whole population; `truncated` says when `people` stops short."
            },
            "required": false,
            "description": "Rows served (default 1000, max 5000). `count` is always the whole population; `truncated` says when `people` stops short.",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The people behind the count",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsPeople"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "metric": "returned_patients",
                    "window": {
                      "from": "2026-09-08",
                      "to": "2026-10-07"
                    },
                    "scope": {
                      "campaign_external_id": null,
                      "ad_external_id": null,
                      "unplaced": false
                    },
                    "count": 1,
                    "people": [
                      {
                        "key": "7d1c2a5e-0000-4000-8000-000000000001",
                        "patient_link": "linked",
                        "patient": {
                          "id": "7d1c2a5e-0000-4000-8000-000000000001",
                          "name": "María Ejemplo Soto",
                          "rut": "11.111.111-1",
                          "phone": "+56911111111"
                        },
                        "contact": {
                          "id": "3f9b6c10-0000-4000-8000-000000000002",
                          "display_name": "María Ejemplo",
                          "key": "0002"
                        },
                        "origin": {
                          "ad_external_id": "120210000000000002",
                          "campaign_external_id": "120210000000000010"
                        },
                        "stage": null,
                        "kind": "returned",
                        "first_at": "2026-09-10",
                        "paid": {
                          "total": 500000,
                          "count": 1,
                          "first_value": 0,
                          "payments": [
                            {
                              "day": "2026-09-10",
                              "amount": 500000,
                              "is_first_payment": false,
                              "kind": "returned",
                              "ad_external_id": "120210000000000002",
                              "campaign_external_id": "120210000000000010"
                            }
                          ]
                        },
                        "conversation": null
                      }
                    ],
                    "truncated": false,
                    "state": "ready",
                    "people_state": "ready"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/creatives": {
      "get": {
        "summary": "Top-performing ads (cohort-normalized creative scoring)",
        "description": "The attribution engine’s top-performing ads — a ROLLING lookback (`window`), not a date range: that is the real endpoint’s own shape. Each ad carries three distinct measures, presented separately — `composite_score` (0-100, a transparent rule blend), `top_performer_likelihood` (0-1, an UNVALIDATED ranker output passed through as the engine sends it — never show it as a probability; Vitrina derives nothing from it, nor from `fatigue_risk_tier`: the wear verdict is the observed `fatigue_state` = `degraded`) and `attributed_revenue`/`roas` (real cash attribution, present when `truth_grade` is `attributed`).\n\nThis is the PROFILE-scoped read — every Vitrina tenant’s delegated key is minted for exactly one attribution profile, so it never reads the engine’s cross-profile ranking.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "14d",
                "28d",
                "lifetime"
              ],
              "description": "A rolling lookback, not a date range — the engine ranks ads over a rolling window, never `from`/`to`. Defaults to `28d`."
            },
            "required": false,
            "description": "A rolling lookback, not a date range — the engine ranks ads over a rolling window, never `from`/`to`. Defaults to `28d`.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "exclusiveMinimum": 0,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` adds each row’s `series[]`: the last 14 days of `{date, spend, ctr}` (the first 50 rows; later rows answer `series: null`)."
            },
            "required": false,
            "description": "`1` adds each row’s `series[]`: the last 14 days of `{date, spend, ctr}` (the first 50 rows; later rows answer `series: null`).",
            "name": "series",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "With `to` (r10 #4): the page period. The conversational line counts and divides over it (from the conversations’ start); the engine still ranks over `window`."
            },
            "required": false,
            "description": "With `to` (r10 #4): the page period. The conversational line counts and divides over it (from the conversations’ start); the engine still ranks over `window`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "With `from`: the page period’s end."
            },
            "required": false,
            "description": "With `from`: the page period’s end.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ],
              "description": "Narrow to the ads of one Línea of the clinic (its id from the Líneas list), or `none` for the ads no rule places in any Línea. An ad belongs to the Línea the clinic’s rules give its campaign, ad set and ad names. Ranks, ratios and totals of the remaining rows are unchanged; `limit` applies before the filter."
            },
            "required": false,
            "description": "Narrow to the ads of one Línea of the clinic (its id from the Líneas list), or `none` for the ads no rule places in any Línea. An ad belongs to the Línea the clinic’s rules give its campaign, ad set and ad names. Ranks, ratios and totals of the remaining rows are unchanged; `limit` applies before the filter.",
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s top-performing ads",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsCreatives"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "ad_external_id": "120212000000000009",
                        "ad_name": "Video testimonio 15s",
                        "campaign_name": "Leads · Septiembre",
                        "campaign": {
                          "external_id": "120211000000000001",
                          "name": "Leads · Septiembre"
                        },
                        "ad_set_name": "Santiago 25-45",
                        "creative_thumbnail_url": null,
                        "video_external_id": "120214000000000009",
                        "ads_manager_url": "https://www.facebook.com/adsmanager/manage/ads?act=<account-id>&selected_ad_ids=120212000000000009",
                        "score_window": "28d",
                        "composite_score": 0.82,
                        "top_performer_likelihood": 0.71,
                        "maturity_stage": "mature",
                        "truth_grade": "attributed",
                        "primary_outcome_kind": "closed_won",
                        "spend": 150000,
                        "impressions": 48200,
                        "ctr": 1.9,
                        "attributed_revenue": 480000,
                        "roas": 3.2,
                        "fatigue_state": "active",
                        "fatigue_risk_tier": "low",
                        "series": [
                          {
                            "date": "2026-09-09",
                            "spend": 6300,
                            "ctr": 1.79
                          },
                          {
                            "date": "2026-09-10",
                            "spend": 7000,
                            "ctr": 1.8
                          },
                          {
                            "date": "2026-09-11",
                            "spend": 5600,
                            "ctr": 1.82
                          },
                          {
                            "date": "2026-09-12",
                            "spend": 4100,
                            "ctr": 1.84
                          },
                          {
                            "date": "2026-09-13",
                            "spend": 3700,
                            "ctr": 1.86
                          },
                          {
                            "date": "2026-09-14",
                            "spend": 6300,
                            "ctr": 1.87
                          },
                          {
                            "date": "2026-09-15",
                            "spend": 7000,
                            "ctr": 1.89
                          },
                          {
                            "date": "2026-09-16",
                            "spend": 5600,
                            "ctr": 1.91
                          },
                          {
                            "date": "2026-09-17",
                            "spend": 6600,
                            "ctr": 1.93
                          },
                          {
                            "date": "2026-09-18",
                            "spend": 6000,
                            "ctr": 1.94
                          },
                          {
                            "date": "2026-09-19",
                            "spend": 3900,
                            "ctr": 1.96
                          },
                          {
                            "date": "2026-09-20",
                            "spend": 4300,
                            "ctr": 1.98
                          },
                          {
                            "date": "2026-09-21",
                            "spend": 5600,
                            "ctr": 2
                          },
                          {
                            "date": "2026-09-22",
                            "spend": 3000,
                            "ctr": 2.01
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/scorecard": {
      "get": {
        "summary": "The one per-ad scorecard (ranking, verdicts, the four returns)",
        "description": "Every ad over the COVERED window (`max(from, the first measured day)`): spend and cash (first + recurring) from the attribution engine, people, bookings, attended visits and accepted presupuestos from Vitrina’s own rows (the conversations each ad opened), the four ratios, a rank and ONE verdict. Every Ads surface that speaks about one ad reads this — the best-ad card, Creativos, the actions and «¿Por qué?» never compute a return of their own.\n\nRanking: accepted-quote return, then cost per person, then bookings. Cash return sorts first only once the window holds `cash_evidence.min_payments` payments credited to ads. An ad with too little spend or too few people is not ranked (`thin`). The top-ranked ad and any ad returning ≥ 1× are never proposed for a pause. Nothing here is a probability.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": false,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": false,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "14d",
                "28d",
                "lifetime"
              ],
              "description": "Instead of `from`/`to`: the Creativos rolling window as dates (ending today, America/Santiago; `lifetime` = the last 366 days, the engine’s longest range). Default `28d` when neither is sent. Either way the numbers count from the first measured day."
            },
            "required": false,
            "description": "Instead of `from`/`to`: the Creativos rolling window as dates (ending today, America/Santiago; `lifetime` = the last 366 days, the engine’s longest range). Default `28d` when neither is sent. Either way the numbers count from the first measured day.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "format": "uuid"
                },
                {
                  "type": "string",
                  "enum": [
                    "none"
                  ]
                }
              ],
              "description": "Narrow to the ads of one Línea of the clinic (its id from the Líneas list), or `none` for the ads no rule places in any Línea. An ad belongs to the Línea the clinic’s rules give its campaign, ad set and ad names. Ranks, ratios and totals of the remaining rows are unchanged; `limit` applies before the filter."
            },
            "required": false,
            "description": "Narrow to the ads of one Línea of the clinic (its id from the Líneas list), or `none` for the ads no rule places in any Línea. An ad belongs to the Línea the clinic’s rules give its campaign, ad set and ad names. Ranks, ratios and totals of the remaining rows are unchanged; `limit` applies before the filter.",
            "name": "linea_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s scorecard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsScorecard"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-09-20",
                      "to": "2026-09-30",
                      "days": 11
                    },
                    "coverage": null,
                    "basis": "accepted",
                    "cash_evidence": {
                      "payments": 3,
                      "min_payments": 5,
                      "used_for_ranking": false
                    },
                    "evidence": {
                      "thin": true,
                      "days": 11,
                      "min_days": 14
                    },
                    "rows": [
                      {
                        "ad_external_id": "120212000000000101",
                        "ad_name": "Implantes 2",
                        "display_name": "Implantes › INTERESES › Implantes 2",
                        "ad_set_name": "INTERESES",
                        "campaign_name": "Implantes",
                        "campaign_external_id": "120210000000000001",
                        "thumbnail_url": null,
                        "video_external_id": null,
                        "ads_manager_url": "https://www.facebook.com/adsmanager/manage/ads?act=<account-id>&selected_ad_ids=120212000000000101",
                        "spend": 57861,
                        "people": 52,
                        "booked": 0,
                        "attended": 0,
                        "accepted": {
                          "count": 1,
                          "value": 1343355
                        },
                        "cash": {
                          "first": 4990,
                          "recurring": 0,
                          "total": 4990,
                          "payments": 1
                        },
                        "fatigue": "active",
                        "ratios": {
                          "accepted_return": 23.22,
                          "cash_return": 0.09,
                          "first_payment_return": 0.09,
                          "cost_per_person": 1113
                        },
                        "lead": {
                          "metric": "accepted",
                          "value": 23.22
                        },
                        "rank": 1,
                        "verdict": "best"
                      }
                    ],
                    "best_ad_external_id": "120212000000000101"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lineas": {
      "get": {
        "summary": "Ads by Línea: spend, people and attributed cash per Línea",
        "description": "The per-ad scorecard (`/ads/scorecard`) summed by the Línea of each ad (D53). An ad belongs to the Línea the clinic’s own rules give its campaign, ad set and ad names (the same rules that set a lead’s Línea at first touch); an ad no rule places is «Sin línea» (`linea_id: null`). Same covered window, same numbers: the Líneas plus «Sin línea» add up to `totals`, which is the scorecard’s own sum. Every live Línea is listed, with zeros when no ad of the window belongs to it. Empty `lineas` for a workspace that is not a clinic. To list one Línea’s ads, send its id as `linea_id` to `/ads/scorecard` or `/ads/creatives`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Ads grouped by Línea",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsByLinea"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-09-20",
                      "to": "2026-09-30",
                      "days": 11
                    },
                    "coverage": null,
                    "lineas": [
                      {
                        "linea_id": "0b1c2d3e-0000-4000-8000-0000000000b1",
                        "nombre": "Implantes",
                        "ads": 2,
                        "spend": 100935,
                        "people": 96,
                        "booked": 4,
                        "attended": 0,
                        "accepted": {
                          "count": 2,
                          "value": 1357355
                        },
                        "cash": {
                          "first": 18990,
                          "recurring": 0,
                          "total": 18990,
                          "payments": 2
                        },
                        "ratios": {
                          "accepted_return": 13.45,
                          "cash_return": 0.19,
                          "first_payment_return": 0.19,
                          "cost_per_person": 1051
                        }
                      },
                      {
                        "linea_id": null,
                        "nombre": null,
                        "ads": 1,
                        "spend": 120088,
                        "people": 37,
                        "booked": 0,
                        "attended": 0,
                        "accepted": null,
                        "cash": null,
                        "ratios": {
                          "accepted_return": null,
                          "cash_return": null,
                          "first_payment_return": null,
                          "cost_per_person": 3246
                        }
                      }
                    ],
                    "totals": {
                      "ads": 3,
                      "spend": 221023,
                      "people": 133,
                      "booked": 4,
                      "attended": 0,
                      "accepted": {
                        "count": 2,
                        "value": 1357355
                      },
                      "cash": {
                        "first": 18990,
                        "recurring": 0,
                        "total": 18990,
                        "payments": 2
                      },
                      "ratios": {
                        "accepted_return": 6.14,
                        "cash_return": 0.09,
                        "first_payment_return": 0.09,
                        "cost_per_person": 1662
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/proyectos": {
      "get": {
        "summary": "Ads by proyecto: cost per lead, visita and reserva",
        "description": "The per-ad scorecard (`/ads/scorecard`) summed by the proyecto each ad sells, for an inmobiliaria. An ad belongs to a proyecto when its id is in the proyecto’s `meta_ad_ids`, else when the proyecto’s name appears in its campaign, ad set or ad name (whole words; the longest name wins; a tie places nobody); an ad no rule places is «Sin proyecto» (`proyecto_id: null`). Each row has spend, leads, visitas agendadas, visitas realizadas, reservas and the cost of each. Same covered window, same numbers: the proyectos plus «Sin proyecto» add up to `totals`, the scorecard’s own sum. Every live proyecto is listed, with zeros when no ad of the window belongs to it. Empty `proyectos` for a workspace with none.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Ads grouped by proyecto",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsByProyecto"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-09-21",
                      "to": "2026-10-20",
                      "days": 30
                    },
                    "coverage": null,
                    "proyectos": [
                      {
                        "proyecto_id": "0b1c2d3e-0000-4000-8000-0000000000c1",
                        "nombre": "Condominio Altos de Lircay",
                        "region": "Región del Maule",
                        "comuna": "Talca",
                        "match": "name",
                        "campaign_names": [
                          "Altos de Lircay · Formulario Meta"
                        ],
                        "ads": 4,
                        "campaigns": 2,
                        "spend": 1925530,
                        "leads": 321,
                        "booked": 39,
                        "attended": 25,
                        "reservas": 5,
                        "reservas_value": 3300000,
                        "costs": {
                          "per_lead": 5999,
                          "per_booked": 49373,
                          "per_attended": 77021,
                          "per_reserva": 385106
                        },
                        "rates": {
                          "lead_to_booked": 0.121,
                          "booked_to_attended": 0.641,
                          "attended_to_reserva": 0.2
                        }
                      },
                      {
                        "proyecto_id": null,
                        "nombre": null,
                        "region": null,
                        "comuna": null,
                        "match": null,
                        "campaign_names": [
                          "Marca · Alcance · Video"
                        ],
                        "ads": 3,
                        "campaigns": 3,
                        "spend": 3683150,
                        "leads": 441,
                        "booked": 36,
                        "attended": 22,
                        "reservas": 1,
                        "reservas_value": 650000,
                        "costs": {
                          "per_lead": 8352,
                          "per_booked": 102310,
                          "per_attended": 167416,
                          "per_reserva": 3683150
                        },
                        "rates": {
                          "lead_to_booked": 0.082,
                          "booked_to_attended": 0.611,
                          "attended_to_reserva": 0.045
                        }
                      }
                    ],
                    "totals": {
                      "ads": 7,
                      "campaigns": 5,
                      "spend": 5608680,
                      "leads": 762,
                      "booked": 75,
                      "attended": 47,
                      "reservas": 6,
                      "reservas_value": 3950000,
                      "costs": {
                        "per_lead": 7360,
                        "per_booked": 74782,
                        "per_attended": 119333,
                        "per_reserva": 934780
                      },
                      "rates": {
                        "lead_to_booked": 0.098,
                        "booked_to_attended": 0.627,
                        "attended_to_reserva": 0.128
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/booking-rate": {
      "get": {
        "summary": "The AI booking rate by conversation, per campaign, ad and hour",
        "description": "Of the conversations Vitrina answered ALONE in the window (the person wrote, the AI agent replied, no team member wrote before the booking), the share where the agent booked a cita — overall, per campaign and ad (the Meta ad that opened the conversation; campaign through the engine’s ad-level map) and per hour of the person’s first message (America/Santiago). The window starts no earlier than the day Vitrina first booked. Rows under `min_conversations` are flagged `low_volume` and ranked after the others. `overall` equals `conversation_rate` on `GET /clinic/insights/resultados` for the same days. Counts only — no person is identified.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The AI booking rate and its breakdowns",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "measured",
                            "not_booking_yet"
                          ],
                          "description": "`not_booking_yet`: Vitrina had not booked any cita by `to` — every block is empty / null, never a 0 %."
                        },
                        "requested": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            }
                          },
                          "required": [
                            "from",
                            "to"
                          ]
                        },
                        "window": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "from": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            }
                          },
                          "required": [
                            "from",
                            "to"
                          ],
                          "description": "The window measured: `requested`, starting no earlier than the first AI booking."
                        },
                        "starts_at_first_booking": {
                          "type": "boolean"
                        },
                        "first_ai_booked_on": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                        },
                        "timezone": {
                          "type": "string"
                        },
                        "min_conversations": {
                          "type": "integer"
                        },
                        "overall": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "conversations": {
                              "type": "integer",
                              "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                            },
                            "booked_conversations": {
                              "type": "integer",
                              "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                            },
                            "ai_bookings": {
                              "type": "integer",
                              "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                            },
                            "rate_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "`booked_conversations ÷ conversations`."
                            },
                            "team_helped": {
                              "type": "integer",
                              "description": "Conversations the AI answered where a team member wrote first — excluded."
                            },
                            "existing": {
                              "type": "object",
                              "properties": {
                                "conversations": {
                                  "type": "integer",
                                  "description": "Conversations Vitrina answered alone with people who had written to the business BEFORE the window (patients’ follow-ups, changes of hour, earlier leads)."
                                },
                                "booked_conversations": {
                                  "type": "integer",
                                  "description": "Of them, those where the agent booked at least one cita (anuladas excluded)."
                                },
                                "ai_bookings": {
                                  "type": "integer",
                                  "description": "Citas the agent booked in them."
                                }
                              },
                              "required": [
                                "conversations",
                                "booked_conversations",
                                "ai_bookings"
                              ],
                              "description": "People who had written before the window (D94): OUTSIDE the rate — its headline counts new people only — and shown on their own line."
                            },
                            "cancelled": {
                              "type": "integer",
                              "description": "Citas the agent booked for new people in the window that were then anuladas — never in `ai_bookings`."
                            }
                          },
                          "required": [
                            "conversations",
                            "booked_conversations",
                            "ai_bookings",
                            "rate_pct",
                            "team_helped",
                            "existing",
                            "cancelled"
                          ],
                          "description": "The same figure as `conversation_rate` on `GET /clinic/insights/resultados` for the same days. NEW PEOPLE ONLY (D94): conversations of people who had written before the window are counted in `existing`, never in the rate — nor in `from_ads`, `without_ad`, `by_campaign`, `by_ad`, `by_hour`."
                        },
                        "from_ads": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "conversations": {
                              "type": "integer",
                              "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                            },
                            "booked_conversations": {
                              "type": "integer",
                              "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                            },
                            "ai_bookings": {
                              "type": "integer",
                              "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                            },
                            "rate_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "`booked_conversations ÷ conversations`."
                            }
                          },
                          "required": [
                            "conversations",
                            "booked_conversations",
                            "ai_bookings",
                            "rate_pct"
                          ],
                          "description": "Conversations a Meta ad opened (its `ad_referral`)."
                        },
                        "without_ad": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "conversations": {
                              "type": "integer",
                              "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                            },
                            "booked_conversations": {
                              "type": "integer",
                              "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                            },
                            "ai_bookings": {
                              "type": "integer",
                              "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                            },
                            "rate_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "`booked_conversations ÷ conversations`."
                            }
                          },
                          "required": [
                            "conversations",
                            "booked_conversations",
                            "ai_bookings",
                            "rate_pct"
                          ]
                        },
                        "campaigns_state": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "unavailable"
                          ],
                          "description": "`unavailable`: the ad → campaign map could not be read; `by_campaign` is empty and every `by_ad[].campaign_external_id` null."
                        },
                        "by_campaign": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "campaign_external_id": {
                                "type": "string",
                                "description": "Meta's campaign id (= `/ads/campaigns` rows)."
                              },
                              "campaign_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "conversations": {
                                "type": "integer",
                                "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                              },
                              "booked_conversations": {
                                "type": "integer",
                                "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                              },
                              "ai_bookings": {
                                "type": "integer",
                                "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                              },
                              "rate_pct": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "`booked_conversations ÷ conversations`."
                              },
                              "low_volume": {
                                "type": "boolean",
                                "description": "Fewer than `min_conversations` conversations: show the rate, never rank it."
                              }
                            },
                            "required": [
                              "campaign_external_id",
                              "campaign_name",
                              "conversations",
                              "booked_conversations",
                              "ai_bookings",
                              "rate_pct",
                              "low_volume"
                            ]
                          },
                          "description": "Ranked by rate (rows with enough volume first), then the low-volume rows by volume."
                        },
                        "unmapped_ad_conversations": {
                          "type": "integer",
                          "description": "Ad-opened conversations whose ad the campaign map does not know."
                        },
                        "by_ad": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "ad_external_id": {
                                "type": "string",
                                "description": "Meta's ad id (= `/ads/creatives` rows)."
                              },
                              "campaign_external_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "conversations": {
                                "type": "integer",
                                "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                              },
                              "booked_conversations": {
                                "type": "integer",
                                "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                              },
                              "ai_bookings": {
                                "type": "integer",
                                "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                              },
                              "rate_pct": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "`booked_conversations ÷ conversations`."
                              },
                              "low_volume": {
                                "type": "boolean",
                                "description": "Fewer than `min_conversations` conversations: show the rate, never rank it."
                              }
                            },
                            "required": [
                              "ad_external_id",
                              "campaign_external_id",
                              "conversations",
                              "booked_conversations",
                              "ai_bookings",
                              "rate_pct",
                              "low_volume"
                            ]
                          }
                        },
                        "by_hour": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "hour": {
                                "type": "integer",
                                "minimum": 0,
                                "maximum": 23
                              },
                              "conversations": {
                                "type": "integer",
                                "description": "Conversations Vitrina answered ALONE with NEW people (they had not written before the window): the person wrote, the AI agent replied after that message, and no team member wrote before the AI booking (or in the whole window, with no booking)."
                              },
                              "booked_conversations": {
                                "type": "integer",
                                "description": "Of `conversations`, those where the agent booked at least one cita inside the window (anuladas excluded)."
                              },
                              "ai_bookings": {
                                "type": "integer",
                                "description": "Citas the agent booked in those conversations (≥ `booked_conversations`)."
                              },
                              "rate_pct": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "`booked_conversations ÷ conversations`."
                              }
                            },
                            "required": [
                              "hour",
                              "conversations",
                              "booked_conversations",
                              "ai_bookings",
                              "rate_pct"
                            ]
                          },
                          "description": "24 rows, by the hour (in `timezone`) of the person's first message in the window."
                        },
                        "truncated": {
                          "type": "boolean"
                        },
                        "rule": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "state",
                        "requested",
                        "window",
                        "starts_at_first_booking",
                        "first_ai_booked_on",
                        "timezone",
                        "min_conversations",
                        "overall",
                        "from_ads",
                        "without_ad",
                        "campaigns_state",
                        "by_campaign",
                        "unmapped_ad_conversations",
                        "by_ad",
                        "by_hour",
                        "truncated",
                        "rule"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "measured",
                    "requested": {
                      "from": "2026-09-01",
                      "to": "2026-10-06"
                    },
                    "window": {
                      "from": "2026-09-21",
                      "to": "2026-10-06"
                    },
                    "starts_at_first_booking": true,
                    "first_ai_booked_on": "2026-09-21",
                    "timezone": "America/Santiago",
                    "min_conversations": 20,
                    "overall": {
                      "conversations": 588,
                      "booked_conversations": 45,
                      "ai_bookings": 54,
                      "rate_pct": 7.7,
                      "team_helped": 60,
                      "existing": {
                        "conversations": 214,
                        "booked_conversations": 38,
                        "ai_bookings": 41
                      },
                      "cancelled": 6
                    },
                    "from_ads": {
                      "conversations": 461,
                      "booked_conversations": 33,
                      "ai_bookings": 40,
                      "rate_pct": 7.2
                    },
                    "without_ad": {
                      "conversations": 127,
                      "booked_conversations": 12,
                      "ai_bookings": 14,
                      "rate_pct": 9.4
                    },
                    "campaigns_state": "ok",
                    "by_campaign": [
                      {
                        "campaign_external_id": "120211000000000001",
                        "campaign_name": "Implantes · Septiembre",
                        "conversations": 212,
                        "booked_conversations": 19,
                        "ai_bookings": 23,
                        "rate_pct": 9,
                        "low_volume": false
                      },
                      {
                        "campaign_external_id": "120211000000000002",
                        "campaign_name": "Endodoncia",
                        "conversations": 12,
                        "booked_conversations": 2,
                        "ai_bookings": 2,
                        "rate_pct": 16.7,
                        "low_volume": true
                      }
                    ],
                    "unmapped_ad_conversations": 3,
                    "by_ad": [
                      {
                        "ad_external_id": "120212000000000009",
                        "campaign_external_id": "120211000000000001",
                        "conversations": 140,
                        "booked_conversations": 14,
                        "ai_bookings": 17,
                        "rate_pct": 10,
                        "low_volume": false
                      }
                    ],
                    "by_hour": [
                      {
                        "hour": 0,
                        "conversations": 9,
                        "booked_conversations": 1,
                        "ai_bookings": 1,
                        "rate_pct": 11.1
                      }
                    ],
                    "truncated": false,
                    "rule": "Conversaciones que el asistente de Vitrina respondió solo y en las que agendó al menos una cita ÷ conversaciones que respondió solo."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/meta-send/comparison": {
      "get": {
        "summary": "The funnel before vs after sending outcomes to Meta",
        "description": "Two windows of equal length either side of the day the workspace first started sending its outcomes to Meta (`/ads/meta-send`): from that day to yesterday, and the same number of days right before. Per window: the AI booking rate by conversation, spend, citas from ads, first-time paying patients from ads and the two costs (the Resumen’s own numbers). `state: collecting` (with `reason`) until both windows are long enough and have enough conversations — the windows are returned, `delta` stays null rather than a misleading difference. Counts and money only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The before/after comparison",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "state": {
                          "type": "string",
                          "enum": [
                            "not_started",
                            "collecting",
                            "ready"
                          ],
                          "description": "`collecting`: not enough data yet (see `reason`) — the windows are returned, `delta` is null."
                        },
                        "reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "too_few_days",
                            "too_few_conversations"
                          ]
                        },
                        "started_on": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                          "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                        },
                        "sending": {
                          "type": "boolean"
                        },
                        "min_days": {
                          "type": "integer"
                        },
                        "min_conversations": {
                          "type": "integer"
                        },
                        "window_days": {
                          "type": "integer",
                          "description": "Whole days in each window (equal lengths)."
                        },
                        "before": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "from": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "ai_booking_covered": {
                              "type": "boolean",
                              "description": "Vitrina was booking through the whole window; when false the rate only counts the days since its first booking and the rate delta is null."
                            },
                            "conversations": {
                              "type": "integer"
                            },
                            "booked_conversations": {
                              "type": "integer"
                            },
                            "rate_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                            },
                            "spend_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "booked": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "Citas from ads (the Resumen `/ads/overview` count)."
                            },
                            "paid": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "First-time paying patients from ads (the Resumen count)."
                            },
                            "cost_per_booked": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cost_per_paid": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "ai_booking_covered",
                            "conversations",
                            "booked_conversations",
                            "rate_pct",
                            "spend_clp",
                            "booked",
                            "paid",
                            "cost_per_booked",
                            "cost_per_paid"
                          ]
                        },
                        "after": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "from": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "Calendar day, `YYYY-MM-DD` (America/Santiago)."
                            },
                            "ai_booking_covered": {
                              "type": "boolean",
                              "description": "Vitrina was booking through the whole window; when false the rate only counts the days since its first booking and the rate delta is null."
                            },
                            "conversations": {
                              "type": "integer"
                            },
                            "booked_conversations": {
                              "type": "integer"
                            },
                            "rate_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Percent, one decimal. `null` — never 0 — with no denominator."
                            },
                            "spend_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "booked": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "Citas from ads (the Resumen `/ads/overview` count)."
                            },
                            "paid": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "First-time paying patients from ads (the Resumen count)."
                            },
                            "cost_per_booked": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "cost_per_paid": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "ai_booking_covered",
                            "conversations",
                            "booked_conversations",
                            "rate_pct",
                            "spend_clp",
                            "booked",
                            "paid",
                            "cost_per_booked",
                            "cost_per_paid"
                          ]
                        },
                        "delta": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "rate_pts": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "after − before, in points."
                            },
                            "cost_per_booked_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "(after − before) ÷ before, percent; negative = cheaper."
                            },
                            "cost_per_paid_pct": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "rate_pts",
                            "cost_per_booked_pct",
                            "cost_per_paid_pct"
                          ]
                        },
                        "rule": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "state",
                        "reason",
                        "started_on",
                        "sending",
                        "min_days",
                        "min_conversations",
                        "window_days",
                        "before",
                        "after",
                        "delta",
                        "rule"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "collecting",
                    "reason": "too_few_days",
                    "started_on": "2026-10-04",
                    "sending": true,
                    "min_days": 14,
                    "min_conversations": 20,
                    "window_days": 3,
                    "before": {
                      "from": "2026-10-01",
                      "to": "2026-10-03",
                      "ai_booking_covered": true,
                      "conversations": 118,
                      "booked_conversations": 9,
                      "rate_pct": 7.6,
                      "spend_clp": 96400,
                      "booked": 11,
                      "paid": 2,
                      "cost_per_booked": 8764,
                      "cost_per_paid": 48200
                    },
                    "after": {
                      "from": "2026-10-04",
                      "to": "2026-10-06",
                      "ai_booking_covered": true,
                      "conversations": 104,
                      "booked_conversations": 8,
                      "rate_pct": 7.7,
                      "spend_clp": 101300,
                      "booked": 10,
                      "paid": 1,
                      "cost_per_booked": 10130,
                      "cost_per_paid": 101300
                    },
                    "delta": null,
                    "rule": "Compara dos períodos del mismo largo: desde el día en que empezaste a enviar tus resultados a Meta hasta ayer, y los mismos días justo antes."
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/funnel": {
      "get": {
        "summary": "The period’s people funnel (a cohort, not event counts)",
        "description": "The people whose FIRST contact falls in the window, and how many of them reached each step: booked → attended → quote presented → accepted → paid. Each step is a subset of the one before (`conversion` = the share that moved on). Split «desde anuncios» (`ads`) vs every other origin (`other`) by the same rule as the origin buckets. People who were already clients before the window are outside the funnel: `earlier` carries their accepted plans and payments in it. Counts only — no person is identified.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s people funnel",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsPeopleFunnel"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-09-01",
                      "to": "2026-09-30"
                    },
                    "steps": [
                      {
                        "key": "contacted",
                        "ads": 215,
                        "other": 76,
                        "total": 291,
                        "conversion": null
                      },
                      {
                        "key": "booked",
                        "ads": 24,
                        "other": 19,
                        "total": 43,
                        "conversion": 0.148
                      },
                      {
                        "key": "attended",
                        "ads": 15,
                        "other": 14,
                        "total": 29,
                        "conversion": 0.674
                      },
                      {
                        "key": "quote_presented",
                        "ads": 11,
                        "other": 10,
                        "total": 21,
                        "conversion": 0.724
                      },
                      {
                        "key": "accepted",
                        "ads": 5,
                        "other": 6,
                        "total": 11,
                        "conversion": 0.524
                      },
                      {
                        "key": "paid",
                        "ads": 3,
                        "other": 5,
                        "total": 8,
                        "conversion": 0.727
                      }
                    ],
                    "earlier": {
                      "people": 30,
                      "accepted_count": 30,
                      "accepted_value": 9412000,
                      "paid_value": 10625399
                    },
                    "credit_known": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/cohorts": {
      "get": {
        "summary": "What a new ad patient costs and pays, by month of arrival",
        "description": "The Informe’s cohort tables. A cohort is the ad patients whose earliest first payment credited to an ad fell in one America/Santiago month of the window. Per cohort: that month’s ad spend, the new patients, the cost per new patient, and the average paid per patient in the arrival month (`m0`), cumulatively by month +1/+2/+3/+5 and to date. A cell for a month that has not come yet is `null` — render «—», never the to-date value. Months before measurement started (`coverage.measured_from`) are `null` throughout.\n\nThe same computation as `/ads/overview`’s `current.patient_return` and `/ads/monthly-return` over the same covered window. `comparison` sets the ad patients who paid in the window beside every other patient; `days_to_first_payment` is the click → first payment median and quartiles. At most 366 days. Aggregates only — no patient is identified.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s acquisition cohorts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsCohorts"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-07-01",
                      "to": "2026-09-30"
                    },
                    "cohorts": [
                      {
                        "month": "2026-07",
                        "measured": true,
                        "spend": 2745532,
                        "patients": 17,
                        "cost_per_new_patient": 161502,
                        "m0": 171988,
                        "m1": 288422,
                        "m2": 292018,
                        "m3": null,
                        "m5": null,
                        "to_date": 292018
                      },
                      {
                        "month": "2026-08",
                        "measured": true,
                        "spend": 2710434,
                        "patients": 25,
                        "cost_per_new_patient": 108417,
                        "m0": 134571,
                        "m1": 180276,
                        "m2": null,
                        "m3": null,
                        "m5": null,
                        "to_date": 180276
                      },
                      {
                        "month": "2026-09",
                        "measured": true,
                        "spend": 1752236,
                        "patients": 11,
                        "cost_per_new_patient": 159294,
                        "m0": 137657,
                        "m1": null,
                        "m2": null,
                        "m3": null,
                        "m5": null,
                        "to_date": 137657
                      }
                    ],
                    "comparison": {
                      "ads": {
                        "patients": 64,
                        "avg_paid": 222124,
                        "avg_payments": 1.9
                      },
                      "other": {
                        "patients": 171,
                        "avg_paid": 341560,
                        "avg_payments": 2.6
                      }
                    },
                    "days_to_first_payment": {
                      "median": 5,
                      "p25": 2,
                      "p75": 11,
                      "patients": 53
                    },
                    "pipeline_uncollected": {
                      "count": 6,
                      "value": 3512400
                    },
                    "joins_state": "ready",
                    "coverage": {
                      "measured_from": "2026-02-19",
                      "touches_from": "2026-02-03",
                      "from": "2026-07-01",
                      "clipped": false,
                      "previous_measured": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/monthly-return": {
      "get": {
        "summary": "What the ad patients paid, month by month, against ad spend",
        "description": "One point per America/Santiago calendar month of the window: what the people who came or came back through an ad paid that month (a conversation an ad opened in the 12 months before each payment) — their first payment (`first_value`) and every later payment (`later_value`, cuotas) — beside the ad spend of the same days. The same computation as `/ads/overview`’s `current.patient_return`, over the same window: the measured months add up to it to the peso (`total`).\n\nMonths that ended before measurement started (`coverage.measured_from`) answer `measured: false` and `null` figures — render «—», never 0. The month measurement starts in counts from that day (`counted_from`). At most 366 days per request. Aggregates only — no patient is identified.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s months",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsMonthlyReturn"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-07-01",
                      "to": "2026-09-30"
                    },
                    "months": [
                      {
                        "month": "2026-07",
                        "from": "2026-07-01",
                        "to": "2026-07-31",
                        "measured": true,
                        "counted_from": "2026-07-01",
                        "partial": false,
                        "spend": 2745532,
                        "value": 4234202,
                        "first_value": 2772678,
                        "later_value": 1461524,
                        "patients": 21,
                        "payments": 30,
                        "roas": 1.542
                      },
                      {
                        "month": "2026-08",
                        "from": "2026-08-01",
                        "to": "2026-08-31",
                        "measured": true,
                        "counted_from": "2026-08-01",
                        "partial": false,
                        "spend": 2710434,
                        "value": 6439688,
                        "first_value": 3515081,
                        "later_value": 2924607,
                        "patients": 27,
                        "payments": 38,
                        "roas": 2.376
                      },
                      {
                        "month": "2026-09",
                        "from": "2026-09-01",
                        "to": "2026-09-30",
                        "measured": true,
                        "counted_from": "2026-09-01",
                        "partial": true,
                        "spend": 1752236,
                        "value": 3542033,
                        "first_value": 1076914,
                        "later_value": 2465119,
                        "patients": 20,
                        "payments": 29,
                        "roas": 2.021
                      }
                    ],
                    "total": {
                      "spend": 7208202,
                      "value": 14215923,
                      "first_value": 7364673,
                      "later_value": 6851250,
                      "patients": 52,
                      "payments": 97,
                      "roas": 1.972
                    },
                    "coverage": {
                      "measured_from": "2026-02-19",
                      "touches_from": "2026-02-03",
                      "from": "2026-07-01",
                      "clipped": false,
                      "previous_measured": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/stages": {
      "get": {
        "summary": "Where the funnel leaks: M1 ads · M2 agenda · M3 clinic sale",
        "description": "The period’s people from ads in three stages, each with its owner: M1 who wrote (and the cost per conversation), M2 who were answered, how fast, who booked and who attended, M3 quotes presented, accepted and paid — the same people as `/ads/funnel`. `budget_cohorts` breaks M3’s quotes down by week of presentation (accepted vs presented, and how many of the accepted were presented in an earlier week). `reference` carries the program’s healthy writing → booking band (10–15 %) and `weakest` names the stage that leaks by a plain rule. Counts only — no person is identified, never a probability.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The window’s three stages",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsStages"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "window": {
                      "from": "2026-09-01",
                      "to": "2026-09-30"
                    },
                    "m1": {
                      "conversations": 215,
                      "spend": 1752236,
                      "cost_per_conversation": 8150
                    },
                    "m2": {
                      "ad_conversations": 226,
                      "answered": 219,
                      "answered_rate": 0.969,
                      "median_first_response_minutes": 4,
                      "booked": 24,
                      "attended": 15,
                      "booked_rate": 0.112,
                      "attended_rate": 0.625,
                      "new_people": {
                        "window": {
                          "from": "2026-09-21",
                          "to": "2026-09-30"
                        },
                        "booking_start": "2026-09-21",
                        "mature_days": 3,
                        "unmeasured": null,
                        "people": 162,
                        "booked": 9,
                        "booked_rate": 0.056,
                        "paid": 2,
                        "paid_rate": 0.222,
                        "replied": 118,
                        "replied_booked": 9,
                        "replied_rate": 0.076,
                        "returned": {
                          "people": 31,
                          "booked": 3
                        }
                      }
                    },
                    "m3": {
                      "quote_presented": 11,
                      "accepted": 5,
                      "paid": 3,
                      "paid_rate": 0.273
                    },
                    "reference": {
                      "m2_booked_rate": {
                        "low": 0.1,
                        "high": 0.15,
                        "label": "program"
                      }
                    },
                    "weakest": null,
                    "credit_known": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/health": {
      "get": {
        "summary": "Attribution trust, UTM quality and attribution coverage",
        "description": "One combined read over three attribution-engine reads, so a dashboard panel needs one call, not three:\n\n* `trust` — how much of the profile’s cash is traceable to an ad (`traceable_pct_by_value`/`_by_count`), whether the instrumentation producing that number is sound (`instrumentation.tracking_healthy`), and which way the traceable half is moving (`traceable_roas_trend`). READ `instrumentation` BEFORE rendering `traceable_pct_by_value` as an indictment — a low number reads as \"great organic\" when tracking is healthy and \"broken tracking\" when it is not.\n* `utm` — the cached UTM-health badge (malformed/missing/dangling-ad-id anomaly counts over the traffic side) plus the dangling-ad-ids rollup (attributed cash that cannot be placed in the ad hierarchy).\n* `attribution_coverage` — the conversion side — how many of the window’s conversions carry a real ad id, an `fbclid` only, or nothing, partitioning `total_conversions`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "The attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "The attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 30,
              "description": "Instrumentation-health + UTM-badge cached window, days (1-30, capped by the Ephemeral Buffer’s retention). Defaults to 30."
            },
            "required": false,
            "description": "Instrumentation-health + UTM-badge cached window, days (1-30, capped by the Ephemeral Buffer’s retention). Defaults to 30.",
            "name": "days",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 2,
              "maximum": 366,
              "description": "Traceable-ROAS trend window, days, split in half for the recent-vs-prior comparison. Defaults to 28."
            },
            "required": false,
            "description": "Traceable-ROAS trend window, days, split in half for the recent-vs-prior comparison. Defaults to 28.",
            "name": "window_days",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Trust, UTM quality and attribution coverage, together",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsHealth"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "trust": {
                      "model": "last_touch",
                      "currency": "CLP",
                      "traceable_pct_by_count": 41.5,
                      "traceable_pct_by_value": 38.2,
                      "total_count": 53,
                      "total_value": 5000000,
                      "instrumentation": {
                        "score": 0.86,
                        "verdict": "healthy_tracking",
                        "tracking_healthy": true,
                        "coverage_interpretation": "La mayoría de las ventas llegan con su origen publicitario.",
                        "missing": [],
                        "tracker": {
                          "status": "live",
                          "web_touches": 1842,
                          "last_web_touch": "2026-08-31T21:14:00Z",
                          "fresh": true
                        }
                      },
                      "traceable_roas_trend": {
                        "recent_roas": 3.4,
                        "prior_roas": 2.9,
                        "recent_spend": 845200,
                        "recent_traceable_revenue": 2873680,
                        "days_with_cash": 11
                      }
                    },
                    "utm": {
                      "scan_window_days": 14,
                      "events_scanned": 312,
                      "high_count": 3,
                      "medium_count": 7,
                      "refreshed_at": "2026-09-22T06:00:00.000Z",
                      "dangling_ad_ids": {
                        "window_days": 28,
                        "model": "last_touch",
                        "dangling_conversions": 2,
                        "attributed_cash": 90000,
                        "reporting_currency": "CLP",
                        "distinct_ad_ids": 1
                      }
                    },
                    "attribution_coverage": {
                      "total_conversions": 44,
                      "with_full_utms": 27,
                      "with_fbclid_only": 5,
                      "with_no_tracking": 12,
                      "coverage_percent": 72.7
                    },
                    "outcomes_coverage": {
                      "outcomes": 42,
                      "attributed_outcomes": 31,
                      "coverage_percent": 73.8
                    },
                    "signals": {
                      "score": 72,
                      "components": [
                        {
                          "key": "meta",
                          "status": "ok",
                          "earned": 30,
                          "max": 30
                        },
                        {
                          "key": "origin",
                          "status": "partial",
                          "earned": 29.5,
                          "max": 40
                        },
                        {
                          "key": "site_tag",
                          "status": "partial",
                          "earned": 12,
                          "max": 30
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/briefing": {
      "get": {
        "summary": "The briefing: ≤3 sentences about a screen’s figures",
        "description": "A short Spanish briefing for one Ads screen, written from PINNED aggregate facts: every figure in `lead`/`lines` is quoted verbatim from `facts[].display`, and `facts` is returned alongside so a client can show «¿Por qué?». Facts are aggregates only — campaign and ad names, never a person.\n\n`source` says who wrote the sentences: `model` (a narrator bound by a guard that rejects any number, name or sentence outside the rules), `template` (built from the facts where the model is never used — first week, Meta disconnected, no spend reading, nothing to report, sample mode — or the model was rejected) or `facts` (built from the facts because no model briefing was cached yet). All read identically.\n\nThe read never waits on the model: a model briefing is cached per facts hash for 24 h (two tabs and a reload read the same text) and precomputed daily for the default windows; on a miss the answer is `source: facts` at once and the model briefing is written in the background, so a later read returns it. `wait=1` writes it live instead (up to ~8 s). Sample mode never calls the model.\n\n`lead.figure` names the number the lead is built around (`display` is its exact substring in `lead.text`). `lines[].ask_key` is the follow-up question a line invites, by recipe key (`best_campaign`, `goal_pace`, `worst_spend`, `missing_signals`, …) — derived from the facts the line quotes, each at most once — and `lines[].ask` its label. A fact that rests on a rule carries it as `threshold`. `from`/`to` are required except for `creativos`, which takes the rolling `window`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "resumen",
                "campanas",
                "atribuidos",
                "creativos",
                "salud"
              ],
              "description": "Which Ads screen the briefing is for."
            },
            "required": true,
            "description": "Which Ads screen the briefing is for.",
            "name": "screen",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD. Required except for `creativos`."
            },
            "required": false,
            "description": "Window start (inclusive), YYYY-MM-DD. Required except for `creativos`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD. Required except for `creativos`."
            },
            "required": false,
            "description": "Window end (inclusive), YYYY-MM-DD. Required except for `creativos`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "14d",
                "28d",
                "lifetime"
              ],
              "description": "`creativos` only: the rolling lookback. Defaults to `28d`."
            },
            "required": false,
            "description": "`creativos` only: the rolling lookback. Defaults to `28d`.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ],
              "description": "`creativos` only: `1` reads the per-ad figures over `from`–`to` (the page period, the same numbers as Resumen); `window` is then only the engine’s nearest rolling read for delivery metrics."
            },
            "required": false,
            "description": "`creativos` only: `1` reads the per-ad figures over `from`–`to` (the page period, the same numbers as Resumen); `window` is then only the engine’s nearest rolling read for delivery metrics.",
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "description": "Attribution model. Defaults to `last_touch`."
            },
            "required": false,
            "description": "Attribution model. Defaults to `last_touch`.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ],
              "description": "On a cache miss, `1` writes the model briefing live (up to ~8 s) instead of answering at once. Without it a miss answers immediately with the sentences built from the facts (`source: facts`) and the model briefing is written in the background for the next read."
            },
            "required": false,
            "description": "On a cache miss, `1` writes the model briefing live (up to ~8 s) instead of answering at once. Without it a miss answers immediately with the sentences built from the facts (`source: facts`) and the model briefing is written in the background for the next read.",
            "name": "wait",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The screen’s briefing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsBriefing"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "screen": "resumen",
                    "window": {
                      "from": "2026-08-24",
                      "to": "2026-09-22"
                    },
                    "lead": {
                      "text": "Tus anuncios devolvieron **3,7×** lo invertido: $845.200 se convirtieron en $3.120.000 en presupuestos aceptados.",
                      "fact_keys": [
                        "roas",
                        "spend",
                        "revenue"
                      ],
                      "figure": {
                        "fact_key": "roas",
                        "value": 3.69,
                        "format": "times",
                        "display": "3,7×",
                        "offset": 27
                      }
                    },
                    "lines": [
                      {
                        "text": "La mejor campaña fue «Evaluación sin costo · Meta», con 5,4× de retorno, y el 36,4% de tus tratamientos llegó desde anuncios.",
                        "fact_keys": [
                          "best_campaign_name",
                          "best_campaign_roas",
                          "ads_share"
                        ],
                        "ask_key": "best_campaign",
                        "ask": "¿Qué campaña me conviene subir?"
                      },
                      {
                        "text": "Revisa «Promoción de temporada · Stories»: gastó $118.400 sin resultados.",
                        "fact_keys": [
                          "worst_spend_campaign_name",
                          "worst_spend_campaign_spend"
                        ],
                        "ask_key": "worst_spend",
                        "ask": "¿Cuánto gasta sin resultados?"
                      }
                    ],
                    "facts": [
                      {
                        "key": "roas",
                        "label": "Retorno",
                        "value": 3.69,
                        "display": "3,7×",
                        "format": "times",
                        "source": "overview.current.roas",
                        "privacy": "aggregate"
                      },
                      {
                        "key": "spend",
                        "label": "Inversión en anuncios",
                        "value": 845200,
                        "display": "$845.200",
                        "format": "clp",
                        "source": "overview.current.spend",
                        "privacy": "aggregate"
                      }
                    ],
                    "actions": [],
                    "source": "model",
                    "generated_at": "2026-09-22T16:40:12.000Z",
                    "vocabulary": "healthcare",
                    "sample": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/goal": {
      "get": {
        "summary": "The workspace’s monthly Ads goal",
        "description": "The goal the Ads hero measures pace against — results from ads, attributed value or return for the calendar month — or `null` when none is set. With `sample=1` answers the sample dataset’s goal.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The goal, or `null`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGoal"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "outcomes",
                    "target": 30,
                    "period": "month",
                    "updated_at": "2026-09-01T12:00:00.000Z",
                    "updated_by": "20000000-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the monthly Ads goal",
        "description": "Replaces the goal. `target` > 0; a `roas` goal is at most 100, a `value` goal at most 10^10 CLP, an `outcomes` goal a whole number. The server stamps `updated_at`/`updated_by`. With `sample=1` the goal is validated and echoed with `persisted: false`, never stored.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "outcomes",
                      "value",
                      "roas"
                    ],
                    "description": "`outcomes` = results from ads this month; `value` = attributed value (CLP); `roas` = return (×)."
                  },
                  "target": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "description": "The month’s target. `roas` ≤ 100; `value` ≤ 10^10; `outcomes` ≤ 10^6."
                  },
                  "period": {
                    "type": "string",
                    "enum": [
                      "month"
                    ],
                    "description": "The only period: the calendar month."
                  }
                },
                "required": [
                  "kind",
                  "target",
                  "period"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "outcomes",
                "target": 30,
                "period": "month"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored goal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/AdsGoal"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "outcomes",
                    "target": 30,
                    "period": "month",
                    "updated_at": "2026-09-23T15:00:00.000Z",
                    "updated_by": "20000000-0000-4000-8000-000000000001",
                    "persisted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Clear the monthly Ads goal",
        "description": "Removes the goal. Idempotent. With `sample=1` nothing is stored or removed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "Cleared"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/share/resumen": {
      "get": {
        "summary": "One calendar month as the Resumen share card (data)",
        "description": "The month the monthly Ads email reports, as data: the card’s dominant figure (the return; the outcomes when there is no value yet; the spend when nothing came back), its caption and lines pre-formatted es-CL in the workspace’s nouns, up to three fact-built sentences (no model) and — when the engine’s recent measurement verdict is broken — the reason, printed after «Falta parte del seguimiento:». `period` = `YYYY-MM` (Santiago), default the previous month. No sample mode.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Calendar month `YYYY-MM` (Santiago). Default: the previous month. The running month reads up to today; a month that has not started yet is a 400."
            },
            "required": false,
            "description": "Calendar month `YYYY-MM` (Santiago). Default: the previous month. The running month reads up to today; a month that has not started yet is a 400.",
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "description": "Palette of the image. Default `light`."
            },
            "required": false,
            "description": "Palette of the image. Default `light`.",
            "name": "theme",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The month card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsShareResumen"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": {
                      "month": "2026-08",
                      "from": "2026-08-01",
                      "to": "2026-08-31",
                      "closed": true,
                      "label": "Agosto · 1 – 31 ago 2026"
                    },
                    "vocabulary": "generic",
                    "has_activity": true,
                    "dominant": "roas",
                    "headline": "Agosto: 3,7× de retorno",
                    "tile": {
                      "number": "3,7×",
                      "unit": "retorno",
                      "caption": "$845.200 invertidos · $3.120.000 en cierres",
                      "lines": [
                        "31 cierres desde anuncios",
                        "Mejor campaña: «Temporada de primavera», 4,2× de retorno",
                        "En julio: 2,9× de retorno"
                      ]
                    },
                    "sentences": [
                      "Tus anuncios trajeron **$3.120.000** en cierres, con 31 cierres (el 73,8% del total).",
                      "La mejor campaña fue «Temporada de primavera», con 4,2× de retorno.",
                      "Son 7 cierres más que en julio."
                    ],
                    "tracking_issue": null,
                    "image_alt": "3,7× retorno en agosto. $845.200 invertidos · $3.120.000 en cierres. 31 cierres desde anuncios. Mejor campaña: «Temporada de primavera», 4,2× de retorno. En julio: 2,9× de retorno",
                    "figures": {
                      "spend": 845200,
                      "revenue": 3120000,
                      "roas": 3.69,
                      "attributed_outcomes": 31,
                      "best_campaign": "Temporada de primavera"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/share/resumen.png": {
      "get": {
        "summary": "One calendar month as the Resumen share card (PNG)",
        "description": "The same card as `GET /ads/share/resumen`, drawn as the Resumen «Compartir» image: a 1200 × 630 PNG (the monthly Ads email carries its own shorter crop of the same facts instead). Aggregates and the workspace’s own campaign names only. Carries a strong `ETag` (a repeat with `If-None-Match` answers 304) and `Cache-Control: private` — a day for a closed month, five minutes for the running one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "description": "Calendar month `YYYY-MM` (Santiago). Default: the previous month. The running month reads up to today; a month that has not started yet is a 400."
            },
            "required": false,
            "description": "Calendar month `YYYY-MM` (Santiago). Default: the previous month. The running month reads up to today; a month that has not started yet is a 400.",
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "light",
                "dark"
              ],
              "description": "Palette of the image. Default `light`."
            },
            "required": false,
            "description": "Palette of the image. Default `light`.",
            "name": "theme",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The card image",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged since the `ETag` sent"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The image rasteriser is not available on this deployment. `error.code` is `SHARE_IMAGE_UNAVAILABLE`; retry after the `Retry-After` seconds. `GET /ads/share/resumen` (the data) is unaffected.",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "SHARE_IMAGE_UNAVAILABLE",
                    "message": "La imagen del resumen no está disponible en este momento. Vuelve a intentarlo en unos minutos."
                  }
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/actions": {
      "get": {
        "summary": "The suggested actions for one Ads screen",
        "description": "Up to 6 suggested actions for a screen, primary first, derived from the screen’s figures and the open recommendations of the measurement engine: pause an ad that «se desgasta» (the observed `fatigue_state: degraded` — on 3 days in a row its trailing 7-day CTR at or under 70 % of its own previous 28 days, or its trailing 7-day cost per result at or over 1/0.7× (+42.86 %); never a learned tier or a probability) AND no longer pays for itself (no attributed value, or a return under 1× — a worn ad still returning 1× or more gets «Prepara una variante» instead, never the pause), pause the delivering ads of a campaign that spent with no results in the period (one card per ad), raise a winning ad set’s budget, re-check the conversion wiring, re-scan the ad links, correct the links of the ads whose «Parámetros de URL» do not carry the ad id (`url_tags_fix`, Salud: one fix over at most 25 ads, busiest first, each ad listed in `target.ads` and its exact change in `params.ads` — the six standard `utm_` parameters take the standard values, replacing the ad’s own; it replaces each ad’s creative, so Meta may restart the learning phase — consent required, undoable per ad) — or, when Meta is not connected, only a `nav` card to reconnect it. Nothing here runs by itself: preview → confirm → execute.\n\n`nav` cards only open a page and never execute (dismissable like any card): Campañas — «Mira quién llegó por …», the lowest cost per result among campaigns with 3 or more results, into the attributed list filtered by that campaign; Creativos — «Prepara una variante de …» for an ad that se desgasta and «Usa … como base» for the best-scored ad that does not, both opening the ad in Meta Ads Manager; Salud — «Instalar el tag en …» (never seen) or «El tag dejó de reportar en …» (installed, silent) when the tag sent nothing in the last 24 hours while Meta is connected, into the set-up’s tag step; «Corregir los enlaces de tus anuncios en Meta» instead of `url_tags_fix` when the change cannot be made from Vitrina (a read-only Meta connection, or a Vitrina connection that needs renewing): it opens those ads in Meta Ads Manager; `params` carries the parameters to paste in place of the current ones.\n\nEvery figure a `why` prints names its window: an ad’s spend is the creatives read’s rolling window («en los últimos 28 días» — what the gallery shows), a campaign’s spend is the requested period.\n\n`window` (Creativos only) derives the rail on the same rolling creatives window as `GET /ads/creatives`; pass the same `window` to preview.\n\nA `key` is `<kind>:<target external id>:<YYYY-MM-DD>` and is stable for the day. Dismissed actions stay on the list with `dismissed_until` set, sorted last. `include=adset_duplicate_swap` also lists the duplicate-and-swap actions (hidden by default). `include=campaign_pause` (Campañas, Resumen) marks a client that renders the whole-campaign pause card (`campaign_pause`: one atomic change in Meta, undone with «Reanudar»). **Not offered yet** — its eligibility rule is pending — so today the list never contains it, with or without the include, and a campaign that spent with no results keeps the per-ad cards.\n\nCard state machine (client): `proposed → previewing → confirming → running → done | failed | refused → (done) reverted`; `dismissed` for 7 days; `simulated` replaces `done` in sample mode.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "resumen",
                "campanas",
                "atribuidos",
                "creativos",
                "salud",
                "envio"
              ],
              "description": "The screen whose action rail to list (`atribuidos` has none). `envio` = «Enviar a Meta»: its owner-initiated changes (`send_setup`, `send_stage`, `send_dataset`, `send_stop`), invoked from the page’s switches — never a rail, not capped."
            },
            "required": true,
            "description": "The screen whose action rail to list (`atribuidos` has none). `envio` = «Enviar a Meta»: its owner-initiated changes (`send_setup`, `send_stage`, `send_dataset`, `send_stop`), invoked from the page’s switches — never a rail, not capped.",
            "name": "screen",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": true,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            },
            "required": true,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "7d",
                "14d",
                "28d",
                "lifetime"
              ],
              "description": "`creativos` only: the creatives window the rail is derived for — the same rolling lookback as `GET /ads/creatives?window=`, so a card’s figures (the worn-out ad’s spend) match the gallery. Defaults to `28d`. Sent with any other screen → 400."
            },
            "required": false,
            "description": "`creativos` only: the creatives window the rail is derived for — the same rolling lookback as `GET /ads/creatives?window=`, so a card’s figures (the worn-out ad’s spend) match the gallery. Defaults to `28d`. Sent with any other screen → 400.",
            "name": "window",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ],
              "description": "`creativos` only: `1` derives the rail over `from`–`to` (the page period); `window` is then only the engine’s nearest rolling read."
            },
            "required": false,
            "description": "`creativos` only: `1` derives the rail over `from`–`to` (the page period); `window` is then only the engine’s nearest rolling read.",
            "name": "period",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "adset_duplicate_swap",
                "campaign_pause"
              ],
              "description": "`adset_duplicate_swap` also lists the duplicate-and-swap actions (Salud). Hidden by default: it rebuilds an ad set (new id, learning reset) and is offered as the alternative after a `promoted_object_frozen` refusal. `campaign_pause` (Campañas, Resumen): the client renders the whole-campaign pause card. Not offered yet (its rule is pending): accepted, and the list does not contain the card today."
            },
            "required": false,
            "description": "`adset_duplicate_swap` also lists the duplicate-and-swap actions (Salud). Hidden by default: it rebuilds an ad set (new id, learning reset) and is offered as the alternative after a `promoted_object_frozen` refusal. `campaign_pause` (Campañas, Resumen): the client renders the whole-campaign pause card. Not offered yet (its rule is pending): accepted, and the list does not contain the card today.",
            "name": "include",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The actions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdsAction"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "key": "pause_ad:120212000000000006:2026-09-23",
                      "kind": "pause_ad",
                      "screen": [
                        "creativos",
                        "resumen",
                        "campanas"
                      ],
                      "primary": true,
                      "title": "Pausar «Video testimonio · 30 s»",
                      "why": "Se desgasta: en sus últimos 7 días, frente a sus 28 días anteriores, el CTR cayó 30 % o más o el costo por resultado subió 43 % o más, durante 3 días seguidos. Lleva **$184.300** de gasto en los últimos 28 días y no le trae presupuestos aceptados.",
                      "effect": "Deja de mostrarse de inmediato; puedes reactivarlo cuando quieras.",
                      "target": {
                        "level": "ad",
                        "external_id": "120212000000000006",
                        "name": "Video testimonio · 30 s",
                        "campaign_external_id": null,
                        "campaign": {
                          "external_id": "120210000000000003",
                          "name": "Blanqueamiento · Septiembre"
                        }
                      },
                      "params": {},
                      "reversible": true,
                      "risk": "reversible",
                      "engine_ref": {
                        "op": "ads.pause"
                      },
                      "facts": [
                        "critical_creative_name",
                        "critical_creative_spend",
                        "fatigued_creatives"
                      ],
                      "dismissed_until": null
                    },
                    {
                      "key": "budget_change:120211000000000077:2026-09-23",
                      "kind": "budget_change",
                      "screen": [
                        "campanas",
                        "resumen"
                      ],
                      "primary": false,
                      "title": "Subir 20 % el presupuesto de «Implantes · Conjunto principal»",
                      "why": "Está trayendo tratamientos a buen costo y tiene espacio para crecer. Es parte de «Implantes · Septiembre», tu campaña con mejor retorno (**4,1×**).",
                      "effect": "El presupuesto diario sube 20 %; puedes deshacerlo.",
                      "target": {
                        "level": "ad_set",
                        "external_id": "120211000000000077",
                        "name": "Implantes · Conjunto principal",
                        "campaign_external_id": null,
                        "campaign": {
                          "external_id": "120210000000000001",
                          "name": "Implantes · Septiembre"
                        }
                      },
                      "params": {
                        "budget_change_pct": 20
                      },
                      "reversible": true,
                      "risk": "reversible",
                      "engine_ref": {
                        "op": "recommendations.apply",
                        "recommendation_id": "a0a0a0a0-0000-4000-8000-000000000001"
                      },
                      "facts": [
                        "best_campaign_name",
                        "best_campaign_roas"
                      ],
                      "dismissed_until": null
                    },
                    {
                      "key": "budget_change:120210000000000002:2026-09-23",
                      "kind": "budget_change",
                      "screen": [
                        "campanas",
                        "resumen"
                      ],
                      "primary": false,
                      "title": "Bajar 20 % el presupuesto de la campaña «Blanqueamiento · Octubre»",
                      "why": "Se lleva el **46 %** del gasto de tus campañas (**$412.000** en septiembre) y agenda el **2,1 %** (3 de 140); «Implantes · Septiembre» agenda el **9,8 %** (12 de 122). La campaña reparte su presupuesto sola, así que se baja ahí: de **$25.000** a **$20.000** al día; 20 % es el paso máximo, y el siguiente, en 3 días.",
                      "effect": "El presupuesto diario de la campaña baja 20 %; puedes deshacerlo.",
                      "target": {
                        "level": "campaign",
                        "external_id": "120210000000000002",
                        "name": "Blanqueamiento · Octubre",
                        "campaign_external_id": null,
                        "campaign": {
                          "external_id": "120210000000000002",
                          "name": "Blanqueamiento · Octubre"
                        }
                      },
                      "params": {
                        "budget_change_pct": -20,
                        "budget_level": "campaign",
                        "budget_type": "daily",
                        "daily_budget": 25000,
                        "new_daily_budget": 20000,
                        "step_pct": 20
                      },
                      "reversible": true,
                      "risk": "reversible",
                      "engine_ref": {
                        "op": "campaigns.budget"
                      },
                      "facts": [],
                      "dismissed_until": null
                    },
                    {
                      "key": "nav:cheapest:120210000000000002:2026-09-23",
                      "kind": "nav",
                      "screen": [
                        "campanas"
                      ],
                      "primary": false,
                      "title": "Mira quién llegó por «Ortodoncia invisible · Sept»",
                      "why": "Es la campaña con el costo más bajo por tratamiento del período: **$19.800** por tratamiento, con 9 tratamientos. Vale la pena ver en qué etapa están antes de moverle presupuesto.",
                      "effect": null,
                      "target": {
                        "level": "campaign",
                        "external_id": "120210000000000002",
                        "name": "Ortodoncia invisible · Sept",
                        "campaign": {
                          "external_id": "120210000000000002",
                          "name": "Ortodoncia invisible · Sept"
                        }
                      },
                      "params": {},
                      "reversible": false,
                      "risk": null,
                      "engine_ref": null,
                      "href": "/anuncios/atribuidos?c=120210000000000002",
                      "cta": "Ver tratamientos",
                      "facts": [],
                      "dismissed_until": null
                    },
                    {
                      "key": "url_tags_fix:profile:2026-09-23",
                      "kind": "url_tags_fix",
                      "screen": [
                        "salud"
                      ],
                      "primary": false,
                      "title": "Corregir los enlaces de tus anuncios",
                      "why": "Así sabrás qué anuncio trajo cada paciente: hoy los clics de **2** anuncios llegan sin decir de cuál vienen.",
                      "effect": "Meta puede reiniciar el aprendizaje de los conjuntos afectados; los anuncios siguen activos. Puedes deshacerlo.",
                      "target": {
                        "level": "profile",
                        "external_id": null,
                        "name": null,
                        "ads": [
                          {
                            "external_id": "120212000000000011",
                            "name": "Carrusel · Antes y después",
                            "ad_set_name": "Implantes · Las Condes",
                            "campaign_name": "Implantes · Septiembre"
                          },
                          {
                            "external_id": "120212000000000012",
                            "name": "Historia · Evaluación gratis",
                            "ad_set_name": "Implantes · Providencia",
                            "campaign_name": "Implantes · Septiembre"
                          }
                        ]
                      },
                      "params": {
                        "ads": [
                          {
                            "external_id": "120212000000000011",
                            "expected_url_tags": "utm_source=facebook&utm_campaign={{campaign.name}}&utm_content={{ad.name}}",
                            "url_tags": "utm_source={{site_source_name}}&utm_medium=paid_social&utm_campaign={{campaign.id}}&utm_term={{adset.id}}&utm_content={{ad.id}}&utm_id={{campaign.id}}"
                          },
                          {
                            "external_id": "120212000000000012",
                            "expected_url_tags": null,
                            "url_tags": "utm_source={{site_source_name}}&utm_medium=paid_social&utm_campaign={{campaign.id}}&utm_term={{adset.id}}&utm_content={{ad.id}}&utm_id={{campaign.id}}"
                          }
                        ]
                      },
                      "reversible": true,
                      "risk": "learning_reset",
                      "engine_ref": {
                        "op": "quality.url_tags_apply"
                      },
                      "facts": [],
                      "dismissed_until": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/actions/{key}/preview": {
      "post": {
        "summary": "Preview an action before confirming it",
        "description": "Reads the target’s live state and returns what will change (`rows`: before → after), the warnings, and — for a duplicate-and-swap or a URL-parameter fix — the consent lines the owner must accept. A `url_tags_fix` preview lists `untagged_ads` («{n} → 0») first, then one row per ad (`ad:<id>`): its current parameters → exactly what it gets; an ad changed in Meta after the preview is skipped at execute, never rewritten blind. A `campaign_pause` preview reads the campaign live ONCE and returns everything its sheet and card draw: `summary` («Pausar «{campaign}» · detiene {n} anuncios · ~$X al día»), `campaign` (status, the ads it stops, the mean daily spend and the budget as exact decimal strings with their currency, and where the budget lives) and the rows `status`, `delivering_ads`, `avg_daily_spend`, `budget`, plus its consent. It also records ONE single-use approval bound to the caller, the action and its exact parameters, valid for 10 minutes (`expires_at`): `preview_id` is what `execute` spends. No engine write happens here.\n\n409 `ADS_ACTION_NOT_AVAILABLE` when the action is no longer on today’s list (or is a `nav` card, which has nothing to execute). A `campaign_pause` key answers it with `details.reason: not_offered` while that action is not offered; once it is, it also answers it when the live read says the pause cannot run now — `details.reason`: `not_active` (already paused), `not_pausable` (archived or deleted), `campaign_not_found`, `meta_connection_missing`, `permission_gap`, `reconnect_required`, `not_available` (any other refusal of the read), `too_recent` (less than 7 complete days of spend) — with `details.explanation`, Spanish copy for the owner; an already-paused, archived or missing campaign also hides the card for 7 days, and a too-recent one until the day it can be judged. 503 `ADS_ACTION_READ_UNAVAILABLE` (`details.reason: meta_unavailable`) when the campaign could not be read right now — retry in a moment. The same 503 with `details.reason: proposal_timeout` when ANY proposal takes more than 8 seconds to prepare: nothing was recorded, retry (the actions a list just returned are kept for 10 minutes, so a review opened from a listed card answers from them instead of re-deriving the list).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 200,
              "pattern": "^[a-z_]+:.+:\\d{4}-\\d{2}-\\d{2}$",
              "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`."
            },
            "required": true,
            "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`.",
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "window": {
                    "type": "string",
                    "enum": [
                      "7d",
                      "14d",
                      "28d",
                      "lifetime"
                    ],
                    "description": "The creatives `window` the card was listed with (`GET /ads/actions?screen=creativos&window=`), so the action is re-derived on the same reads. Defaults to `28d`."
                  },
                  "sample": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "additionalProperties": false
              },
              "example": {
                "from": "2026-08-24",
                "to": "2026-09-23"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsActionPreview"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "preview_id": "b1b1b1b1-0000-4000-8000-000000000001",
                    "expires_at": "2026-09-23T15:10:00.000Z",
                    "action_key": "budget_change:120211000000000077:2026-09-23",
                    "rows": [
                      {
                        "key": "daily_budget",
                        "label": "Presupuesto diario",
                        "before": "$10.000",
                        "after": "$12.000"
                      }
                    ],
                    "warnings": [
                      "Solo se permite cambiar el presupuesto hasta 50 % por vez."
                    ],
                    "consent": [],
                    "consent_terms": null,
                    "reversible": true,
                    "engine_op": "recommendations.apply"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`campaign_pause` only: the campaign could not be read right now (no answer, an upstream error or a rate limit). `error.code` is `ADS_ACTION_READ_UNAVAILABLE`, `details.reason: meta_unavailable`, `details.explanation` Spanish copy for the owner. Nothing was recorded; retry in a moment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/actions/{key}/execute": {
      "post": {
        "summary": "Confirm and run a previewed action",
        "description": "Spends the preview’s single-use approval and queues the engine write; poll `GET /ads/actions/executions/{id}`. **`Idempotency-Key` is required** (400 without): a replay with the same key answers the first execution and writes nothing.\n\n409 `ADS_ACTION_REPLAY_BLOCKED` when the approval cannot be spent — `details.reason` is `expired` (preview older than 10 minutes), `consumed` (already confirmed, e.g. from another tab), `not_found` or `binding_mismatch` (another person’s preview, or another action — incl. a real preview sent with `sample: true`). In sample mode nothing reaches the engine: the execution is born `succeeded` with `simulated: true`, and only a sample preview can be spent.\n\n**`campaign_pause`**, **duplicate-and-swap** and **`url_tags_fix`** need the owner’s acceptance of the preview’s `consent_terms` (the verbatim consent text): send `consent: { version, text_hash, accepted_at? }`. Missing, different or older than 24 h → 409 `ADS_ACTION_CONSENT_REQUIRED` (`details.reason` missing|mismatch|stale, `details.consent` = the current version and hash) — nothing is spent and nothing reaches the engine.\n\nIf the job cannot be queued the answer is still 202, with `status: failed`: the execution exists and says why (`error.code: queue_unavailable`).\n\nA `url_tags_fix` corrects each ad on its own: `outcome.summary` reads «{k} de {n} anuncios corregidos», `outcome.partial` is true when k < n, and `outcome.ads[]` says what happened to each ad. None corrected → `failed`/`refused` with the first ad’s reason, still with `outcome.ads[]`.\n\nA `campaign_pause` is ONE change in Meta. Each execution is its own request: a second confirm (another tab, an agent) finds the campaign already paused and answers `outcome.engine_outcome: no_change` — nothing written, no journal line, nothing to undo. Otherwise `engine_outcome` is `applied`, or `replayed` (a retry of this same execution); `outcome.affected_ads_count` is how many ads stopped.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 200,
              "pattern": "^[a-z_]+:.+:\\d{4}-\\d{2}-\\d{2}$",
              "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`."
            },
            "required": true,
            "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`.",
            "name": "key",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$",
              "description": "REQUIRED. 8–200 characters `[A-Za-z0-9_-]`, one per confirm click; a replay returns the first execution. A key already used for another request (an execute key on a rollback, say) is `409 IDEMPOTENCY_KEY_CONFLICT`."
            },
            "required": true,
            "description": "REQUIRED. 8–200 characters `[A-Za-z0-9_-]`, one per confirm click; a replay returns the first execution. A key already used for another request (an execute key on a rollback, say) is `409 IDEMPOTENCY_KEY_CONFLICT`.",
            "name": "Idempotency-Key",
            "in": "header"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "preview_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The `preview_id` the preview returned (single use, 10 minutes)."
                  },
                  "sample": {
                    "type": "boolean"
                  },
                  "consent": {
                    "type": "object",
                    "properties": {
                      "version": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "text_hash": {
                        "type": "string",
                        "pattern": "^[0-9a-fA-F]{64}$"
                      },
                      "accepted_at": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When the owner accepted (ISO 8601; defaults to now). At most 24 h old."
                      }
                    },
                    "required": [
                      "version",
                      "text_hash"
                    ],
                    "additionalProperties": false,
                    "description": "Required whenever the preview returned `consent_terms` (a campaign pause `campaign_pause`, a duplicate-and-swap, a URL-parameter fix `url_tags_fix`, «Enviar a Meta»'s `send_setup` and `send_dataset`, and a `send_stage` off / `send_stop` that stops an event active ad sets optimise on), ignored otherwise: the owner's acceptance of `consent_terms` — echo its `version` and `text_hash`. The accepting person is always the caller."
                  }
                },
                "required": [
                  "preview_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "preview_id": "b1b1b1b1-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued (or the replayed execution)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "execution_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "running",
                            "succeeded",
                            "failed",
                            "refused",
                            "rolled_back"
                          ]
                        }
                      },
                      "required": [
                        "execution_id",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "execution_id": "e5e5e5e5-0000-4000-8000-000000000001",
                    "status": "queued"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ads/actions/executions": {
      "get": {
        "summary": "The actions journal («Lo que hice»)",
        "description": "Every action execution of the workspace (execute and rollback rows), newest first, each with what it touched (`target`) and who confirmed it (`created_by`, a display name — only for a caller holding `memberships:read`). `from`/`to` bound `created_at` by America/Santiago day; page with `before=<meta.next_before>`. Real mode lists real executions only; with `sample=1` it lists what was simulated plus two example rows of today (also readable on `GET /ads/actions/executions/{id}`; not undoable: `rollback_available: false`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "First day (inclusive, America/Santiago) on `created_at`."
            },
            "required": false,
            "description": "First day (inclusive, America/Santiago) on `created_at`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Last day (inclusive, America/Santiago) on `created_at`."
            },
            "required": false,
            "description": "Last day (inclusive, America/Santiago) on `created_at`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120,
              "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?Z(,[0-9a-f-]{36})?$",
              "description": "Paging cursor: a previous page’s `meta.next_before`, verbatim."
            },
            "required": false,
            "description": "Paging cursor: a previous page’s `meta.next_before`, verbatim.",
            "name": "before",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "description": "Rows per page (default 20, max 50)."
            },
            "required": false,
            "description": "Rows per page (default 20, max 50).",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "envio"
              ],
              "description": "Only one screen’s executions. `envio` = «Enviar a Meta»: `send_setup`, `send_stage`, `send_dataset`, `send_stop`, `send_protected_move` and their rollbacks. Omitted = every execution."
            },
            "required": false,
            "description": "Only one screen’s executions. `envio` = «Enviar a Meta»: `send_setup`, `send_stage`, `send_dataset`, `send_stop`, `send_protected_move` and their rollbacks. Omitted = every execution.",
            "name": "screen",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the journal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdsActionExecutionListItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "next_before": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Opaque cursor (`<created_at>,<id>`): pass as `before`."
                        }
                      },
                      "required": [
                        "next_before"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "e5e5e5e5-0000-4000-8000-000000000003",
                      "action_key": "pause_ad:120212000000000006:2026-09-23",
                      "kind": "pause_ad",
                      "status": "succeeded",
                      "outcome": {
                        "summary": "Anuncio pausado en Meta.",
                        "engine_action_id": "f7f7f7f7-0000-4000-8000-000000000002",
                        "applied_at": "2026-09-23T16:20:04.000Z"
                      },
                      "error": null,
                      "rollback_available": true,
                      "rollback_of": null,
                      "simulated": false,
                      "created_at": "2026-09-23T16:20:01.000Z",
                      "finished_at": "2026-09-23T16:20:04.000Z",
                      "target": {
                        "level": "ad",
                        "external_id": "120212000000000006",
                        "name": "Blanqueamiento · Sonrisa",
                        "campaign_name": "Blanqueamiento · Septiembre"
                      },
                      "created_by": "Dueña Ejemplo"
                    },
                    {
                      "id": "e5e5e5e5-0000-4000-8000-000000000004",
                      "action_key": "url_tags_fix:profile:2026-09-23",
                      "kind": "url_tags_fix",
                      "status": "succeeded",
                      "outcome": {
                        "summary": "1 de 2 anuncios corregidos.",
                        "engine_action_id": "f7f7f7f7-0000-4000-8000-000000000003",
                        "applied_at": "2026-09-23T15:40:09.000Z",
                        "partial": true,
                        "corrected_count": 1,
                        "requested_count": 2,
                        "ads": [
                          {
                            "external_id": "120212000000000011",
                            "name": "Carrusel · Antes y después",
                            "result": "corrected",
                            "code": null,
                            "message": null,
                            "url_tags_after": "utm_source={{site_source_name}}&utm_medium=paid_social&utm_campaign={{campaign.id}}&utm_term={{adset.id}}&utm_content={{ad.id}}&utm_id={{campaign.id}}",
                            "engine_action_id": "f7f7f7f7-0000-4000-8000-000000000003",
                            "undo_available": true
                          },
                          {
                            "external_id": "120212000000000012",
                            "name": "Historia · Evaluación gratis",
                            "result": "skipped",
                            "code": "url_tags_changed_since_preview",
                            "message": "Los parámetros de URL del anuncio cambiaron en Meta después de la vista previa, así que no lo tocamos. Revisa la propuesta de nuevo.",
                            "url_tags_after": null,
                            "engine_action_id": null,
                            "undo_available": false
                          }
                        ]
                      },
                      "error": null,
                      "rollback_available": true,
                      "rollback_of": null,
                      "simulated": false,
                      "created_at": "2026-09-23T15:40:01.000Z",
                      "finished_at": "2026-09-23T15:40:09.000Z",
                      "target": {
                        "level": "profile",
                        "external_id": null,
                        "name": null,
                        "campaign_name": null,
                        "ads": [
                          {
                            "external_id": "120212000000000011",
                            "name": "Carrusel · Antes y después"
                          },
                          {
                            "external_id": "120212000000000012",
                            "name": "Historia · Evaluación gratis"
                          }
                        ]
                      },
                      "created_by": "Dueña Ejemplo"
                    },
                    {
                      "id": "e5e5e5e5-0000-4000-8000-000000000001",
                      "action_key": "budget_change:120211000000000077:2026-09-23",
                      "kind": "budget_change",
                      "status": "succeeded",
                      "outcome": {
                        "summary": "Cambio aplicado en Meta.",
                        "engine_action_id": "f7f7f7f7-0000-4000-8000-000000000001",
                        "applied_at": "2026-09-23T15:01:12.000Z"
                      },
                      "error": null,
                      "rollback_available": true,
                      "rollback_of": null,
                      "simulated": false,
                      "created_at": "2026-09-23T15:01:10.000Z",
                      "finished_at": "2026-09-23T15:01:12.000Z",
                      "target": {
                        "level": "ad_set",
                        "external_id": "120211000000000077",
                        "name": "Implantes · Las Condes",
                        "campaign_name": "Implantes · Septiembre"
                      },
                      "created_by": null
                    }
                  ],
                  "meta": {
                    "next_before": "2026-09-23T15:01:10.000000Z,e5e5e5e5-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/actions/executions/{id}": {
      "get": {
        "summary": "The status of an action execution",
        "description": "`queued → running → succeeded | failed | refused`; a succeeded execution becomes `rolled_back` when its rollback succeeds. `error.reason` is one of `promoted_object_frozen` (Meta froze the ad set’s conversion — the duplicate-and-swap is the alternative), `reconnect_required`, `permission_gap` (Meta connected without permission to manage ads — reconnect keeping it), `rate_limited`, `not_reversible`, `forbidden` (no person could be put on the record for the change — not a Meta grant problem), `consent_required` (the duplicate-and-swap consent is missing or stale — preview again), `engine_error` (incl. `code: budget_change_out_of_bounds` — outside the ±50 % allowed per change — `code: not_active` and `code: no_recommendation`). Campaign pause codes: `campaign_not_found` / `campaign_not_pausable` (archived or deleted — never written) / `meta_refused` (`engine_error`), `campaign_not_resumable` (`not_reversible`), a dead Meta token → `reconnect_required`, `meta_permission_denied` (`permission_gap` — Meta refused for a missing permission), `applied_by_not_allowed` (`forbidden`), `meta_rate_limited` (`rate_limited`, retried first). `error.message` is Spanish copy ready for the card. `target` is what the action touched (with `campaign_name`, the campaign the card named, on an ad-set budget change).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The execution id."
            },
            "required": true,
            "description": "The execution id.",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The execution",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsActionExecution"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e5e5e5e5-0000-4000-8000-000000000001",
                    "action_key": "budget_change:120211000000000077:2026-09-23",
                    "kind": "budget_change",
                    "status": "succeeded",
                    "target": {
                      "level": "ad_set",
                      "external_id": "120211000000000077",
                      "name": "Implantes · Las Condes",
                      "campaign_name": "Implantes · Septiembre"
                    },
                    "outcome": {
                      "summary": "Cambio aplicado en Meta.",
                      "engine_action_id": "f7f7f7f7-0000-4000-8000-000000000001",
                      "applied_at": "2026-09-23T15:01:12.000Z"
                    },
                    "error": null,
                    "rollback_available": true,
                    "rollback_of": null,
                    "simulated": false,
                    "created_at": "2026-09-23T15:01:10.000Z",
                    "finished_at": "2026-09-23T15:01:12.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/actions/executions/{id}/rollback": {
      "post": {
        "summary": "Undo an executed action",
        "description": "Queues the engine’s rollback of a `succeeded` execution with `rollback_available: true` and answers the NEW execution (`rollback_of` = the original). The engine restores the pre-change state only while the object still carries what the action wrote; otherwise the rollback is `refused` with `reason: not_reversible` (`code: state_drifted`). A `url_tags_fix` is undone ad by ad (each ad back on its previous parameters): an ad changed in Meta since is left alone (final), an ad whose undo got no answer stays pending — the original keeps `rollback_available: true` and the next rollback sends only those ads; the original reads `rolled_back` once nothing is pending. A `campaign_pause` is undone with «Reanudar»: one resume in Meta, with a fresh key per request; ads that were paused on their own stay paused. A campaign already active answers `outcome.engine_outcome: no_change` (the pause is then no longer undoable, never marked as undone by Vitrina); an archived or deleted one is `refused` `not_reversible` (`code: campaign_not_resumable`), and so is one paused again by someone else since (`code: paused_since`): «Reanudar» only ever undoes this pause. **`Idempotency-Key` is required.** 409 `ADS_ACTION_NOT_REVERSIBLE` when the execution has nothing to undo.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The execution id."
            },
            "required": true,
            "description": "The execution id.",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$",
              "description": "REQUIRED. 8–200 characters `[A-Za-z0-9_-]`, one per confirm click; a replay returns the first execution. A key already used for another request (an execute key on a rollback, say) is `409 IDEMPOTENCY_KEY_CONFLICT`."
            },
            "required": true,
            "description": "REQUIRED. 8–200 characters `[A-Za-z0-9_-]`, one per confirm click; a replay returns the first execution. A key already used for another request (an execute key on a rollback, say) is `409 IDEMPOTENCY_KEY_CONFLICT`.",
            "name": "Idempotency-Key",
            "in": "header"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "The rollback execution",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "execution_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "running",
                            "succeeded",
                            "failed",
                            "refused",
                            "rolled_back"
                          ]
                        }
                      },
                      "required": [
                        "execution_id",
                        "status"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "execution_id": "e5e5e5e5-0000-4000-8000-000000000002",
                    "status": "queued"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ads/actions/{key}/dismiss": {
      "post": {
        "summary": "Hide an action for a few days",
        "description": "Hides the action from the rail for `days` (default 7, max 30): it stays on `GET /ads/actions` with `dismissed_until` set and sorted last. Stored in the workspace’s Ads settings; not persisted in sample mode (`persisted: false`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 5,
              "maxLength": 200,
              "pattern": "^[a-z_]+:.+:\\d{4}-\\d{2}-\\d{2}$",
              "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`."
            },
            "required": true,
            "description": "The action key: `<kind>:<target external id>:<YYYY-MM-DD>`.",
            "name": "key",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 30,
                    "default": 7
                  },
                  "sample": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "days": 7
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dismissed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "dismissed_until": {
                          "type": "string"
                        },
                        "persisted": {
                          "type": "boolean",
                          "description": "`false` in sample mode."
                        }
                      },
                      "required": [
                        "dismissed_until",
                        "persisted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dismissed_until": "2026-09-30T15:00:00.000Z",
                    "persisted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/key": {
      "get": {
        "summary": "The workspace's tracking key and the tag to paste",
        "description": "Ensures an active tracking key exists for the workspace (minting one on the first call — idempotent, a repeat never mints a second key) and returns the tag to paste. `combined_snippet` is THE tag: Vitrina's one-line loader (`<script src=\".../tag.js?site=pk_…\">`), which boots the assistant and the measurement from the same tag. Its site key (`site_key`) is the newest unexpired publishable key of the workspace, or — when it has none — one minted on this read for the workspace's Vitrina site, registered domains and declared website, and listed afterwards among its publishable keys. `combined_snippet` and `site_key` are `null` only when no origin can be derived at all.\n\n`snippet` is the measurement provider's raw tracker script (the tracking half alone), kept for API consumers that self-host it; Vitrina's own UI renders `combined_snippet` only. `meta_pixel_id` is present when the workspace has a Meta pixel configured.\n\n402 `ENTITLEMENT_NOT_ACTIVE` (`feature: vitrina_ads`) when Vitrina Ads is not active for this workspace; 409 `ADS_KEY_NEEDS_REMINT` when the Add-on is on but the delegated key has not finished rotating. Admin-scoped (`ads:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The tracking key and the tag to paste",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsTrackingKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "key_id": "abababab-0000-4000-8000-000000000001",
                    "public_key": "trk_live_abc123",
                    "snippet": "<script src=\"https://track.example.com/t.js?k=trk_live_abc123\" async></script>",
                    "combined_snippet": "<script src=\"https://api.vitrinadev.com/tag.js?site=pk_live_sitekey000000000000\" async></script>",
                    "site_key": "pk_live_sitekey000000000000",
                    "meta_pixel_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/installers/gtm": {
      "get": {
        "summary": "The tag as a ready-to-paste Google Tag Manager installer",
        "description": "The tag name, \"All Pages\" trigger, snippet and setup checklist for a Google Tag Manager Custom HTML tag. The `snippet` is the COMBINED loader (`<script src=\".../tag.js?site=…\">`): pasting this ONE tag boots the assistant AND starts the measurement. `tag_name` and `setup_checklist` are Vitrina-branded user-facing copy (Spanish) that names only `tag.js`.\n\n`site_id` (optional) is the publishable `pk_…` key of the site the tag is being installed on — embedded into the snippet's `?site=` query so the tag survives GTM stripping `data-*` off the `<script>` tag it re-creates. Absent → the workspace's own site key (the `site_key` of `GET /ads/tracking/key`, minted on first read); 400 when the workspace has none and no origin to mint one for.\n\nSame entitlement gate as `GET /ads/tracking/key`. Admin-scoped (`ads:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": false,
            "name": "site_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The GTM Custom HTML installer payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmInstaller"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "key_id": "abababab-0000-4000-8000-000000000001",
                    "public_key": "trk_live_abc123",
                    "site_key": "pk_live_sitekey000000000000",
                    "tag_name": "Vitrina Ads · tag",
                    "trigger": "All Pages",
                    "snippet": "<script src=\"https://api.vitrinadev.com/tag.js?site=pk_live_sitekey000000000000\" async></script>",
                    "setup_checklist": [
                      "En Google Tag Manager, crea una etiqueta nueva de tipo «HTML personalizado».",
                      "Pega el fragmento de Vitrina (tag.js) tal cual.",
                      "Activador: All Pages.",
                      "Guarda la etiqueta y publica el contenedor.",
                      "El asistente y la medición se cargan desde este mismo tag.js: no agregues otro fragmento de Vitrina aparte."
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/status": {
      "get": {
        "summary": "Is the tag installed? Last event seen, events in 24 h, domain",
        "description": "Tag detection for the workspace's tracking key. `source: tracker` (the normal case) reads the measurement provider's per-key last-event time (`last_seen_at`), the tracker events of the last 24 hours (`events_24h`) and the hostname of the latest verified install check (`domain`). `installed` is `true` only when the tag sent an event in the last 30 days — never without a timestamp — and it STAYS `true` for 30 days after the last event, so it does not mean «live»: `reporting` does (events in the last 24 hours).\n\nWhen the per-key read is unavailable the answer falls back to the health read's daily UTM scan: `source: health`, `installed` = the refreshed scan counted events, `reporting: false` (unknown), `last_seen_at` / `events_24h` / `domain` = `null`. `events_24h` is `null` (never `0`) whenever the count could not be read.\n\nSame entitlement gate as `GET /ads/tracking/key`, except that `sample=1` (or a sandbox workspace) answers a fixed sample status (`installed: true`, `reporting: true`, seen minutes ago, `source: tracker`) without the entitlement and without reading the real tag — like every other Ads read. Admin-scoped (`ads:read`).\n\n`booking_widget` is the clinic booking widget’s own install check (#3709): seen when a page loads the widget with the clinic’s `pk_` key; `null` when the workspace has no clinic widget to install.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The tag detection status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsTrackingStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "installed": true,
                    "reporting": true,
                    "last_seen_at": "2026-09-23T14:02:11.000Z",
                    "events_24h": 184,
                    "domain": "www.clinica-ejemplo.cl",
                    "source": "tracker"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/gtm": {
      "get": {
        "summary": "Google Tag Manager install: connection, container, install state",
        "description": "Where the programmatic Google Tag Manager install of the Vitrina Ads tag stands: whether Google is connected (and with which Tag Manager permissions — `can_edit`, `can_publish`), the chosen web container, and the install state (`none` → `draft` → `published`, or `removed`). A database read — never calls Google.\n\n`available: false` means this deployment cannot connect Google: use the manual installer (`GET /ads/tracking/installers/gtm`). Verify the tag is live with `GET /ads/tracking/status`. Not gated by the Ads add-on, so a workspace that turned Ads off can still see and remove its tag. Admin-scoped (`ads:read`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The GTM install state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "draft",
                      "workspace_path": "accounts/1234567890/containers/987654321/workspaces/7",
                      "workspace_url": "https://tagmanager.google.com/#/container/accounts/1234567890/containers/987654321/workspaces/7",
                      "tag_path": "accounts/1234567890/containers/987654321/workspaces/7/tags/12",
                      "trigger_path": "accounts/1234567890/containers/987654321/workspaces/7/triggers/11",
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/gtm/connect": {
      "post": {
        "summary": "Start the Google consent for Tag Manager",
        "description": "Returns the Google consent URL to send the browser to. `access: edit` asks the minimal Tag Manager permissions (read containers, write the tag, preview); `access: publish` adds creating and publishing container versions — asked incrementally, the first time the admin publishes (a `409 GTM_CONSENT_REQUIRED` with `details.access: publish` says when). After the consent the browser lands on `return_to` with `?gtm_code=…&gtm_state=…` — finish with `POST /ads/tracking/gtm/connect/complete` — or with `?gtm_error=` `consent_denied` | `oauth_failed`.\n\n`return_to` must be on the app's own origin. Tokens are stored encrypted and never returned. Admin-scoped (`ads:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsGtmConnectBody"
              },
              "example": {
                "return_to": "https://app.example.com/w/example/ads",
                "access": "edit"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The consent URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmConnect"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=<client-id>&scope=<scopes>&state=<state>"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "`GTM_NOT_CONFIGURED` — this deployment has no Google OAuth client (`GET /ads/tracking/gtm` answers `available: false`)."
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/tracking/gtm/connect/complete": {
      "post": {
        "summary": "Finish the Google consent for Tag Manager",
        "description": "Exchanges the `code` the consent returned (`gtm_code` / `gtm_state` on the return URL) and stores the Google tokens encrypted. The signed `state` must belong to the CALLER — the same workspace, and the same person when it recorded one — otherwise `403`: a consent link started by someone else can never land a Google grant in their workspace. Single use. Answers the new state (`connection.can_edit` / `can_publish` read the scopes Google actually granted). Admin-scoped (`ads:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsGtmConnectCompleteBody"
              },
              "example": {
                "code": "<code>",
                "state": "<state>"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The state after connecting",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": null,
                    "install": {
                      "state": "none",
                      "workspace_path": null,
                      "workspace_url": null,
                      "tag_path": null,
                      "trigger_path": null,
                      "site_key": null,
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          },
          "503": {
            "description": "`GTM_NOT_CONFIGURED` — this deployment has no Google OAuth client (`GET /ads/tracking/gtm` answers `available: false`)."
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/tracking/gtm/connection": {
      "delete": {
        "summary": "Disconnect Google Tag Manager",
        "description": "Revokes the Google grant and forgets the tokens. The container choice and install facts stay, so reconnecting later can still uninstall the tag. Does not touch the container. Admin-scoped (`ads:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The state after disconnecting",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "not_connected",
                      "account_email": null,
                      "can_edit": false,
                      "can_publish": false
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "draft",
                      "workspace_path": "accounts/1234567890/containers/987654321/workspaces/7",
                      "workspace_url": "https://tagmanager.google.com/#/container/accounts/1234567890/containers/987654321/workspaces/7",
                      "tag_path": "accounts/1234567890/containers/987654321/workspaces/7/tags/12",
                      "trigger_path": "accounts/1234567890/containers/987654321/workspaces/7/triggers/11",
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ads/tracking/gtm/containers": {
      "get": {
        "summary": "The Tag Manager web containers the Google account can reach",
        "description": "Every Tag Manager account of the connected Google account with its **web** containers. `suggested` marks the containers whose domains match the site's (the site key's allowed origins and the workspace's declared website); `suggested_path` is the one to pre-select. Live Tag Manager read. Admin-scoped (`ads:read`), Ads gate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "Accounts and their web containers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmContainers"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "accounts": [
                      {
                        "path": "accounts/1234567890",
                        "name": "Example",
                        "containers": [
                          {
                            "path": "accounts/1234567890/containers/987654321",
                            "code": "GTM-XXXXXXX",
                            "name": "example.com",
                            "domains": [
                              "example.com"
                            ],
                            "suggested": true
                          }
                        ]
                      }
                    ],
                    "suggested_path": "accounts/1234567890/containers/987654321",
                    "site_hosts": [
                      "example.com"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/gtm/container": {
      "put": {
        "summary": "Choose the Tag Manager container to install into",
        "description": "Stores the container (a `path` from `GET /ads/tracking/gtm/containers`). Only web containers are accepted. Choosing a different container resets the install state. Admin-scoped (`ads:write`), Ads gate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsGtmSelectContainerBody"
              },
              "example": {
                "container_path": "accounts/1234567890/containers/987654321"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The state with the chosen container",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "none",
                      "workspace_path": null,
                      "workspace_url": null,
                      "tag_path": null,
                      "trigger_path": null,
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/tracking/gtm/install": {
      "post": {
        "summary": "Write the Vitrina Ads tag into the container (unpublished)",
        "description": "Creates (or reuses) a dedicated workspace «Vitrina Ads» and writes ONE Custom HTML tag «Vitrina Ads · tag» (tag type `html`, the `snippet` of `GET /ads/tracking/installers/gtm` verbatim) fired by ONE page-view trigger (type `pageview`, All Pages). Idempotent: re-running updates the same tag and never duplicates it. Nothing is published.\n\n`review` lists what else is pending: changes in the workspace that are not Vitrina’s (they would ride along on publish), other workspaces with unpublished changes (never published by Vitrina), and other tags that already load `tag.js`. Admin-scoped (`ads:write`), Ads gate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The state, with the workspace review",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "draft",
                      "workspace_path": "accounts/1234567890/containers/987654321/workspaces/7",
                      "workspace_url": "https://tagmanager.google.com/#/container/accounts/1234567890/containers/987654321/workspaces/7",
                      "tag_path": "accounts/1234567890/containers/987654321/workspaces/7/tags/12",
                      "trigger_path": "accounts/1234567890/containers/987654321/workspaces/7/triggers/11",
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": {
                      "pending_changes": [],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 2
                        }
                      ],
                      "duplicate_loader_tags": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/tracking/gtm/preview": {
      "post": {
        "summary": "Preview the tag on the site (Tag Assistant)",
        "description": "Writes the tag (as `install`) and quick-previews the «Vitrina Ads» workspace. `install.preview_url` is the Tag Assistant link that opens the site with the workspace in preview — only the person who opens it sees the tag. Nothing is published. Admin-scoped (`ads:write`), Ads gate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The state with the preview link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "draft",
                      "workspace_path": "accounts/1234567890/containers/987654321/workspaces/7",
                      "workspace_url": "https://tagmanager.google.com/#/container/accounts/1234567890/containers/987654321/workspaces/7",
                      "tag_path": "accounts/1234567890/containers/987654321/workspaces/7/tags/12",
                      "trigger_path": "accounts/1234567890/containers/987654321/workspaces/7/triggers/11",
                      "site_key": "<site-key>",
                      "preview_url": "https://tagassistant.google.com/#/?source=TAG_MANAGER&id=GTM-XXXXXXX&url=https%3A%2F%2Fexample.com",
                      "previewed_at": "2026-09-29T15:00:00.000Z",
                      "published_version_path": null,
                      "published_at": null
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/tracking/gtm/publish": {
      "post": {
        "summary": "Publish the tag (new container version)",
        "description": "Writes the tag (as `install`), syncs the workspace, creates a container version from it and publishes that version. Needs the `publish` consent (`409 GTM_CONSENT_REQUIRED`, `details.access: publish`, otherwise).\n\n**Never publishes someone else’s changes silently.** Merge conflicts refuse with `409 GTM_WORKSPACE_CONFLICT`; changes in the workspace that are not Vitrina’s refuse with `409 GTM_PENDING_CHANGES` listing them, until the call is repeated with `include_pending_changes: true` after a person has seen that list. Changes in other workspaces are never part of the version. Idempotent: an already-published, unchanged tag publishes nothing. Then verify with `GET /ads/tracking/status`. Admin-scoped (`ads:write`), Ads gate.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsGtmPublishBody"
              },
              "example": {
                "include_pending_changes": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The published state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "published",
                      "workspace_path": null,
                      "workspace_url": null,
                      "tag_path": null,
                      "trigger_path": null,
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": "accounts/1234567890/containers/987654321/versions/42",
                      "published_at": "2026-09-29T15:04:05.000Z"
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/tracking/gtm/uninstall": {
      "post": {
        "summary": "Remove the tag (new container version)",
        "description": "Deletes the Vitrina tag and its trigger in the «Vitrina Ads» workspace, then creates and publishes a container version — with the same pending-changes guard as `publish`. When the container holds no Vitrina tag there is nothing to publish. Not gated by the Ads add-on: a workspace that turned Ads off can still remove its tag. Needs the `publish` consent. Admin-scoped (`ads:write`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdsGtmPublishBody"
              },
              "example": {
                "include_pending_changes": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The state after removing the tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsGtmState"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "available": true,
                    "connection": {
                      "status": "connected",
                      "account_email": "admin@example.com",
                      "can_edit": true,
                      "can_publish": true
                    },
                    "container": {
                      "path": "accounts/1234567890/containers/987654321",
                      "code": "GTM-XXXXXXX",
                      "name": "example.com",
                      "domains": [
                        "example.com",
                        "www.example.com"
                      ]
                    },
                    "install": {
                      "state": "removed",
                      "workspace_path": null,
                      "workspace_url": null,
                      "tag_path": null,
                      "trigger_path": null,
                      "site_key": "<site-key>",
                      "preview_url": null,
                      "previewed_at": null,
                      "published_version_path": "accounts/1234567890/containers/987654321/versions/42",
                      "published_at": "2026-09-29T15:04:05.000Z"
                    },
                    "review": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A state the caller answers, by `error.code`: `GTM_NOT_CONNECTED` (connect Google again), `GTM_CONSENT_REQUIRED` (`details.access` names the consent step to ask for), `GTM_PERMISSION_DENIED` (the Google account lacks the container permission in Tag Manager), `GTM_CONTAINER_REQUIRED`, `GTM_PENDING_CHANGES` (`details.pending_changes` + `details.other_workspaces` — show them, then retry with `include_pending_changes: true` and `pending_fingerprint` = `details.fingerprint`), `GTM_WORKSPACE_CONFLICT` (`details.conflicts`; resolve in Tag Manager) or `GTM_WORKSPACE_LIMIT`. Also `ADS_KEY_NEEDS_REMINT` on the gated operations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {}
                  }
                },
                "example": {
                  "error": {
                    "code": "GTM_PENDING_CHANGES",
                    "message": "The workspace holds changes that are not Vitrina’s — review them and confirm to publish them too",
                    "details": {
                      "pending_changes": [
                        {
                          "type": "tag",
                          "name": "Example tag",
                          "change": "updated"
                        }
                      ],
                      "other_workspaces": [
                        {
                          "name": "Default Workspace",
                          "pending_changes": 3
                        }
                      ],
                      "fingerprint": "<fingerprint>",
                      "changed_since_confirmation": false
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "`UPSTREAM_ERROR` — Google Tag Manager answered an error, or could not compile the workspace (`details.compiler_error`)."
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/feed": {
      "get": {
        "summary": "The live feed of recorded outcomes",
        "description": "Every outcome Vitrina recorded for the workspace (a lead, a booking, an attended visit, a presented quote, a closed sale, a payment), newest first — Vitrina’s own ledger, so a row appears the moment it happens, not after the engine’s daily sync. A row names the campaign the attribution engine credited (`ad`, from a cache refreshed every 5 minutes over the requested window, up to 92 days back; `null` until then — `meta.enrichment` says whether it is complete — with `matched: true` that reads «anuncio por confirmar»).\n\n`contact.display_name` is filled only for a caller that also holds `contacts:read`; everyone else gets `null` and `contact.key` (the last 4 hex of the contact id).\n\nPolling: call with `since=<newest created_at seen>` (verbatim); the answer re-includes the 5 seconds before it, so dedupe by `id`. Paging: `before=<meta.next_before>` (verbatim; when a polling page comes back full, page down with `before` + the same `since` to close the gap). `only_ads=1` / `campaign_id` keep the rows the engine credited (to that campaign). With `sample=1` answers the sample dataset’s feed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive, Santiago day), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window start (inclusive, Santiago day), YYYY-MM-DD.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive, Santiago day), YYYY-MM-DD."
            },
            "required": true,
            "description": "Window end (inclusive, Santiago day), YYYY-MM-DD.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "lead_created",
                "web_form",
                "lead_qualified",
                "appointment_booked",
                "checkout_started",
                "deposit_paid",
                "appointment_attended",
                "quote_presented",
                "closed_won",
                "payment_received"
              ],
              "description": "Only outcomes of this stage."
            },
            "required": false,
            "description": "Only outcomes of this stage.",
            "name": "stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` keeps only outcomes the engine credited to a campaign (the enrichment covers the requested window, up to 92 days back; check `meta.enrichment`)."
            },
            "required": false,
            "description": "`1` keeps only outcomes the engine credited to a campaign (the enrichment covers the requested window, up to 92 days back; check `meta.enrichment`).",
            "name": "only_ads",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "Only outcomes credited to this campaign (its PLATFORM id)."
            },
            "required": false,
            "description": "Only outcomes credited to this campaign (its PLATFORM id).",
            "name": "campaign_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?Z$",
              "description": "Polling cursor: pass back the newest `created_at` seen, verbatim. The answer re-includes the 5 seconds before it (an outcome committed late with an earlier timestamp is not lost) — dedupe by `id`."
            },
            "required": false,
            "description": "Polling cursor: pass back the newest `created_at` seen, verbatim. The answer re-includes the 5 seconds before it (an outcome committed late with an earlier timestamp is not lost) — dedupe by `id`.",
            "name": "since",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 80,
              "description": "Paging cursor: `meta.next_before` of the previous page, verbatim (`<created_at>,<id>` — the id breaks ties between rows recorded in the same instant). Combine with `since` to fill a gap after a full polling page."
            },
            "required": false,
            "description": "Paging cursor: `meta.next_before` of the previous page, verbatim (`<created_at>,<id>` — the id breaks ties between rows recorded in the same instant). Combine with `since` to fill a gap after a full polling page.",
            "name": "before",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "description": "Page size, 1–50. Defaults to 30."
            },
            "required": false,
            "description": "Page size, 1–50. Defaults to 30.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of the feed, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AdsFeedItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "next_before": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Opaque cursor (`<created_at>,<id>`): pass as `before` for the next (older) page; `null` at the end."
                        },
                        "server_time": {
                          "type": "string"
                        },
                        "conversion_source": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "crm": {
                              "$ref": "#/components/schemas/AdsConversionSourceCrm"
                            },
                            "payments": {
                              "$ref": "#/components/schemas/AdsConversionSourcePayments"
                            }
                          },
                          "required": [
                            "crm",
                            "payments"
                          ],
                          "description": "The workspace’s Conversion source (see `GET /ads/conversion-source`); `null` when it could not be read this time — unknown, never «Vitrina»."
                        },
                        "enrichment": {
                          "type": "string",
                          "enum": [
                            "ready",
                            "pending",
                            "unavailable"
                          ],
                          "description": "Whether the campaign credit on this page is complete: `pending` — still being read, retry shortly (a matched row may show `ad: null` and `only_ads` may be short); `unavailable` — the attribution engine could not be read in the last minute."
                        },
                        "period_counts": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "total": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "from_ads": {
                              "type": "integer",
                              "minimum": 0,
                              "description": "Of them, the rows credited to an ad (`only_ads`)."
                            },
                            "kept": {
                              "type": "integer",
                              "minimum": 0,
                              "description": "Of them, the rows recorded and kept in Vitrina, never sent to Meta (a minor’s fact, one reached only through a guardian, an erased contact). `total − kept` is what «Envío a Meta» counts."
                            },
                            "recorded_from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "The first Santiago day of the whole record (any stage) when it falls inside the period; `null` = the record covers the period."
                            },
                            "by_stage": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "stage": {
                                    "type": "string",
                                    "enum": [
                                      "lead_created",
                                      "web_form",
                                      "lead_qualified",
                                      "appointment_booked",
                                      "checkout_started",
                                      "deposit_paid",
                                      "appointment_attended",
                                      "quote_presented",
                                      "closed_won",
                                      "first_payment",
                                      "returning_first_payment",
                                      "payment_received"
                                    ]
                                  },
                                  "total": {
                                    "type": "integer",
                                    "minimum": 0
                                  },
                                  "from_ads": {
                                    "type": "integer",
                                    "minimum": 0
                                  },
                                  "kept": {
                                    "type": "integer",
                                    "minimum": 0
                                  },
                                  "recorded_from": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                                    "description": "The stage’s first recorded Santiago day when it falls inside the period (the count starts there, «desde el 25 sep»); `null` = the stage is recorded for the whole period."
                                  }
                                },
                                "required": [
                                  "stage",
                                  "total",
                                  "from_ads",
                                  "kept",
                                  "recorded_from"
                                ]
                              }
                            }
                          },
                          "required": [
                            "total",
                            "from_ads",
                            "kept",
                            "recorded_from",
                            "by_stage"
                          ],
                          "description": "The WHOLE period’s rows per stage (the same rows the feed pages through, unfiltered) — what a stage chip counts, never the loaded page. On the first page only (no `before`, no `since`); `null` = the count could not be read."
                        }
                      },
                      "required": [
                        "next_before",
                        "server_time",
                        "conversion_source",
                        "enrichment"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                      "stage": "closed_won",
                      "occurred_at": "2026-09-22T19:40:12.000Z",
                      "created_at": "2026-09-22T19:40:13.482913Z",
                      "matched": true,
                      "value": 1200000,
                      "action_source": "business_messaging",
                      "contact": {
                        "id": "5b0c2f1e-8a3d-4c6b-9e7f-1a2b3c4d5e6f",
                        "display_name": "Paciente Ejemplo",
                        "key": "5e6f"
                      },
                      "ad": {
                        "name": null,
                        "ad_set_name": null,
                        "creative_thumbnail_url": null,
                        "campaign": {
                          "external_id": "120211000000000001",
                          "name": "Ortodoncia invisible · Septiembre"
                        }
                      },
                      "credited_weight": 1,
                      "touch_count": 3,
                      "origin": {
                        "platform": "whatsapp",
                        "external_id": "120211000000000101",
                        "name": "Reel · Antes y después",
                        "ad_name": "Reel · Antes y después",
                        "headline": "Evaluación sin costo",
                        "campaign": {
                          "external_id": "120211000000000001",
                          "name": "Ortodoncia invisible · Septiembre"
                        },
                        "thumbnail_url": null,
                        "media_archived": false,
                        "first_message_at": "2026-09-20T13:02:30.000Z",
                        "matches_credit": true
                      },
                      "origin_source": "engine",
                      "attribution": "credited"
                    },
                    {
                      "id": "01926f3a-1b2c-7d3e-8f40-5a6b7c8d9e0f",
                      "stage": "appointment_booked",
                      "occurred_at": "2026-09-22T18:02:55.000Z",
                      "created_at": "2026-09-22T18:02:56.104220Z",
                      "matched": true,
                      "value": null,
                      "action_source": "business_messaging",
                      "contact": {
                        "id": "7c1d3e2f-9b4a-4d5c-8e6f-2b3c4d5e6f70",
                        "display_name": null,
                        "key": "6f70"
                      },
                      "ad": null,
                      "credited_weight": null,
                      "touch_count": null,
                      "origin": {
                        "platform": "instagram",
                        "external_id": "120211000000000205",
                        "name": "Limpieza facial",
                        "ad_name": null,
                        "headline": "Limpieza facial",
                        "campaign": null,
                        "thumbnail_url": null,
                        "media_archived": false,
                        "first_message_at": "2026-09-22T17:40:05.000Z",
                        "matches_credit": null
                      },
                      "origin_source": "conversation",
                      "attribution": "pending"
                    }
                  ],
                  "meta": {
                    "next_before": "2026-09-22T18:02:56.104220Z,01926f3a-1b2c-7d3e-8f40-5a6b7c8d9e0f",
                    "server_time": "2026-09-22T19:41:00.000Z",
                    "enrichment": "ready"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/feed/{id}/journey": {
      "get": {
        "summary": "One feed row’s journey",
        "description": "The journey behind one recorded outcome, oldest first: the attribution engine’s touches (the ad — its name, ad set, campaign and thumbnail — the click that landed, site visits) interleaved with the steps Vitrina recorded itself (first message, booking, attended visit, presented quote, acceptance, payments). The credited touch is `credited: true` with its `join_method`; the row itself is `current: true`. Labels are ready to render in the workspace’s vocabulary, and `stage` carries the vocabulary key.\n\n`scope=conversion` (default) ends at this outcome; `scope=person` is the whole person — every touch and every recorded step, across repeat visits and devices — plus `person.attended_count` («vino n veces») and `person.conversion_count`.\n\nNever a 5xx for the engine: when its touches cannot be shown the journey is Vitrina’s own steps (`source: vitrina`) and `reason` says why (`unmatched` / `pending` / `unavailable` / `scope`). Never carries a URL, path, referrer, device, location, email or the engine’s own person ids. 404 when the id is not a live row of this workspace. With `sample=1` answers the sample dataset’s journey (pass the feed’s `from`/`to` when it was not the last 30 days).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The feed item id (`data[].id`)."
            },
            "required": true,
            "description": "The feed item id (`data[].id`).",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "conversion",
                "person"
              ],
              "description": "`conversion` (default): the path to THIS outcome — the touches before it and the contact’s recorded steps up to it. `person`: the whole person — every touch and every recorded step, across repeat visits and devices — plus `person` (how many times they came, how many conversions). Use it for «Ver recorrido completo»."
            },
            "required": false,
            "description": "`conversion` (default): the path to THIS outcome — the touches before it and the contact’s recorded steps up to it. `person`: the whole person — every touch and every recorded step, across repeat visits and devices — plus `person` (how many times they came, how many conversions). Use it for «Ver recorrido completo».",
            "name": "scope",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Sample mode only: the feed window the item came from (defaults to the last 30 days). Ignored for real data."
            },
            "required": false,
            "description": "Sample mode only: the feed window the item came from (defaults to the last 30 days). Ignored for real data.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Sample mode only — see `from`."
            },
            "required": false,
            "description": "Sample mode only — see `from`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter."
            },
            "required": false,
            "description": "`1` serves the deterministic sample dataset («Ver con datos de ejemplo»): same shapes, invented but internally consistent figures, no entitlement required (the scope still is). A sandbox workspace is always served the sample, with or without this parameter.",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The journey",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsJourney"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "steps": [
                      {
                        "stage": null,
                        "detail": "Ortodoncia invisible · Septiembre",
                        "credited": false,
                        "join_method": null,
                        "click_id_kind": null,
                        "ad": {
                          "external_id": "120211000000000101",
                          "name": "Reel · Antes y después",
                          "ad_set_name": "Mujeres 30-45 · Providencia",
                          "campaign": {
                            "external_id": "120211000000000100",
                            "name": "Ortodoncia invisible · Septiembre"
                          },
                          "creative_thumbnail_url": null
                        },
                        "value": null,
                        "current": false,
                        "id": "b2f0c1d4-1a2b-4c3d-8e4f-5a6b7c8d9e01",
                        "kind": "ad",
                        "origin": "engine",
                        "label": "Reel · Antes y después",
                        "at": "2026-09-19T21:10:00.000Z",
                        "channel": "meta"
                      },
                      {
                        "stage": null,
                        "detail": "Reel · Antes y después",
                        "credited": true,
                        "join_method": "click_id",
                        "click_id_kind": "fbclid",
                        "ad": {
                          "external_id": "120211000000000101",
                          "name": "Reel · Antes y después",
                          "ad_set_name": "Mujeres 30-45 · Providencia",
                          "campaign": {
                            "external_id": "120211000000000100",
                            "name": "Ortodoncia invisible · Septiembre"
                          },
                          "creative_thumbnail_url": null
                        },
                        "value": null,
                        "current": false,
                        "id": "b2f0c1d4-1a2b-4c3d-8e4f-5a6b7c8d9e02",
                        "kind": "click",
                        "origin": "engine",
                        "label": "Visita desde el anuncio",
                        "at": "2026-09-20T13:02:00.000Z",
                        "channel": "meta"
                      },
                      {
                        "stage": null,
                        "detail": "Ortodoncia invisible · Septiembre",
                        "credited": false,
                        "join_method": "ctwa_referral",
                        "click_id_kind": "ctwa_clid",
                        "ad": {
                          "external_id": "120211000000000101",
                          "name": "Reel · Antes y después",
                          "ad_set_name": "Mujeres 30-45 · Providencia",
                          "campaign": {
                            "external_id": "120211000000000100",
                            "name": "Ortodoncia invisible · Septiembre"
                          },
                          "creative_thumbnail_url": null
                        },
                        "value": null,
                        "current": false,
                        "id": "7d7d7d7d-1111-4222-8333-444455556666",
                        "kind": "chat",
                        "origin": "vitrina",
                        "label": "Mensaje por el anuncio «Reel · Antes y después»",
                        "at": "2026-09-20T13:02:30.000Z",
                        "channel": "whatsapp"
                      },
                      {
                        "stage": "lead_created",
                        "detail": "Por WhatsApp",
                        "credited": false,
                        "join_method": null,
                        "click_id_kind": null,
                        "ad": null,
                        "value": null,
                        "current": false,
                        "id": "01926f3e-1111-7b2e-9f10-3a5b7c9d0e10",
                        "kind": "chat",
                        "origin": "vitrina",
                        "label": "Contacto nuevo",
                        "at": "2026-09-20T13:03:00.000Z",
                        "channel": "whatsapp"
                      },
                      {
                        "stage": "appointment_attended",
                        "detail": null,
                        "credited": false,
                        "join_method": null,
                        "click_id_kind": null,
                        "ad": null,
                        "value": null,
                        "current": false,
                        "id": "01926f3e-2222-7b2e-9f10-3a5b7c9d0e11",
                        "kind": "attended",
                        "origin": "vitrina",
                        "label": "Cita atendida",
                        "at": "2026-09-21T14:00:00.000Z"
                      },
                      {
                        "stage": "quote_presented",
                        "detail": null,
                        "credited": false,
                        "join_method": null,
                        "click_id_kind": null,
                        "ad": null,
                        "value": 1200000,
                        "current": false,
                        "id": "01926f3e-3333-7b2e-9f10-3a5b7c9d0e12",
                        "kind": "quote",
                        "origin": "vitrina",
                        "label": "Presupuesto presentado",
                        "at": "2026-09-21T15:20:00.000Z"
                      },
                      {
                        "stage": "closed_won",
                        "detail": null,
                        "credited": false,
                        "join_method": null,
                        "click_id_kind": null,
                        "ad": null,
                        "value": 1200000,
                        "current": true,
                        "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                        "kind": "accepted",
                        "origin": "vitrina",
                        "label": "Presupuesto aceptado",
                        "at": "2026-09-22T19:40:12.000Z"
                      }
                    ],
                    "touch_count": 2,
                    "credited_weight": 1,
                    "model": "last_touch",
                    "scope": "conversion",
                    "source": "mixed",
                    "reason": null,
                    "person": null,
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-source": {
      "get": {
        "summary": "Where bookings, sales and payments come from",
        "description": "The workspace’s Conversion source for Vitrina Ads, derived on every read from its live connections and never stored. `crm`: the CRM the workspace connected (GoHighLevel) while that connection is live — otherwise `vitrina`, Vitrina’s own bookings and sales, which take over the moment the CRM is disconnected. `payments`: the connected payment provider (Mercado Pago) while live — otherwise `vitrina`. A `needs_reconnect` connection is still the source; its data stops until it is reconnected. Connect or disconnect a provider with `/integrations/connections/{provider}/…`. The same object rides on `/ads/overview` (`conversion_source`) and `/ads/feed` (`meta.conversion_source`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ],
              "description": "Accepted like on every Ads read; the answer is the workspace’s real Conversion source either way (it is a fact about its connections)."
            },
            "required": false,
            "description": "Accepted like on every Ads read; the answer is the workspace’s real Conversion source either way (it is a fact about its connections).",
            "name": "sample",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The Conversion source in effect",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsConversionSource"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "crm": {
                      "source": "gohighlevel",
                      "display_name": "GoHighLevel",
                      "connection_state": "connected"
                    },
                    "payments": {
                      "source": "mercadopago",
                      "display_name": "Mercado Pago",
                      "connection_state": "connected"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/experiments": {
      "get": {
        "summary": "The optimisation experiment: offer and history",
        "description": "Whether the «Probar optimizar por primer pago» experiment can be started now (`offer`: only while outcomes reach Meta and the dataset is healthy, one experiment at a time) and the workspace’s experiments, newest first. An experiment compares ad sets that optimise for different events — today’s «conversaciones» against ad sets optimising on a Vitrina event such as the first payment.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The offer and the experiments.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "offer": {
                          "$ref": "#/components/schemas/AdsExperimentOffer"
                        },
                        "experiments": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AdsExperiment"
                          }
                        }
                      },
                      "required": [
                        "offer",
                        "experiments"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "offer": {
                      "available": false,
                      "reason": "already_open",
                      "suggested_test_event": "appointment_booked",
                      "weekly_first_payments": 12,
                      "first_payment_threshold": 50
                    },
                    "experiments": [
                      {
                        "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                        "name": "Conversaciones vs primer pago",
                        "status": "running",
                        "started_at": "2026-10-02T13:00:00.000Z",
                        "ended_at": null,
                        "readout_from": null,
                        "read_early": false,
                        "created_at": "2026-10-01T21:10:00.000Z",
                        "arms": [
                          {
                            "arm_key": "control",
                            "role": "control",
                            "optimization_event": "conversations",
                            "meta_campaign": null,
                            "meta_ad_sets": [
                              "120211000000000201"
                            ],
                            "budget_share": 50,
                            "learning_status": "active",
                            "learning_checked_at": "2026-10-03T12:00:00.000Z",
                            "left_learning_at": "2026-10-02T18:00:00.000Z"
                          },
                          {
                            "arm_key": "first_payment",
                            "role": "test",
                            "optimization_event": "first_payment",
                            "meta_campaign": "120211000000000300",
                            "meta_ad_sets": [
                              "120211000000000301"
                            ],
                            "budget_share": 50,
                            "learning_status": "learning",
                            "learning_checked_at": "2026-10-03T12:00:00.000Z",
                            "left_learning_at": null
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Record an optimisation experiment (draft)",
        "description": "Records the control (today’s «conversaciones» ad sets) and the test arm (ad sets optimising on `test_event`; default, D81: the offer’s `suggested_test_event` — «cita agendada» until ~50 first payments a week, then the first payment). No Meta write: Meta’s API cannot re-optimise a published click-to-WhatsApp ad set on a purchase (the Sales objective belongs to the campaign and a published ad set’s optimisation is frozen), so the test arm is built in Ads Manager and its ids recorded here (now or with PATCH). 409 when not offered (`details.reason`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "test_event": {
                    "type": "string",
                    "enum": [
                      "first_payment",
                      "closed_won",
                      "appointment_attended",
                      "appointment_booked",
                      "lead_created"
                    ],
                    "description": "The test arm’s event; default `first_payment`."
                  },
                  "control_ad_sets": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[0-9]{1,32}$"
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "Today’s «conversaciones» ad sets (the control)."
                  },
                  "test_ad_sets": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^[0-9]{1,32}$"
                    },
                    "maxItems": 20,
                    "description": "The ad sets built to optimise on `test_event`, when already built in Ads Manager; otherwise recorded later with PATCH."
                  },
                  "test_campaign": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{1,32}$"
                  },
                  "test_budget_share": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "exclusiveMinimum": 0,
                    "exclusiveMaximum": 100
                  }
                },
                "required": [
                  "control_ad_sets"
                ],
                "additionalProperties": false
              },
              "example": {
                "control_ad_sets": [
                  "120211000000000201"
                ],
                "test_ad_sets": [
                  "120211000000000301"
                ],
                "test_campaign": "120211000000000300",
                "test_event": "first_payment",
                "test_budget_share": 50
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The draft experiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsExperiment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                    "name": "Conversaciones vs primer pago",
                    "status": "draft",
                    "started_at": null,
                    "ended_at": null,
                    "readout_from": null,
                    "read_early": false,
                    "created_at": "2026-10-01T21:10:00.000Z",
                    "arms": [
                      {
                        "arm_key": "control",
                        "role": "control",
                        "optimization_event": "conversations",
                        "meta_campaign": null,
                        "meta_ad_sets": [
                          "120211000000000201"
                        ],
                        "budget_share": 50,
                        "learning_status": "active",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": "2026-10-02T18:00:00.000Z"
                      },
                      {
                        "arm_key": "first_payment",
                        "role": "test",
                        "optimization_event": "first_payment",
                        "meta_campaign": "120211000000000300",
                        "meta_ad_sets": [
                          "120211000000000301"
                        ],
                        "budget_share": 50,
                        "learning_status": "learning",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/experiments/current": {
      "get": {
        "summary": "The experiment in flight, with its status",
        "description": "The workspace’s open experiment (draft or running) and its readout in one read — «Prueba en curso: día {day_number} de {planned_days}», spend and cost per paying patient on each side. `experiment: null` and `readout: null` when none is open (then the recommendations offer «Duplicar con el evento nuevo»).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The open experiment and its readout, or nulls.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "experiment": {
                          "allOf": [
                            {
                              "$ref": "#/components/schemas/AdsExperiment"
                            },
                            {
                              "type": [
                                "object",
                                "null"
                              ],
                              "description": "The experiment in flight (draft or running); `null` = none."
                            }
                          ]
                        },
                        "readout": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "phase": {
                              "type": "string",
                              "enum": [
                                "setup",
                                "learning",
                                "comparing",
                                "ended"
                              ],
                              "description": "`setup` (test arm not recorded) → `learning` (an arm is still learning; no numbers) → `comparing` → `ended`."
                            },
                            "from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "days_of_data": {
                              "type": "integer"
                            },
                            "day_number": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "«Día N de 28»: days since the start, 1 on the start day, while the test runs; `null` before it starts and once it ended."
                            },
                            "planned_days": {
                              "type": "integer",
                              "description": "The planned length: 28."
                            },
                            "learning_finished": {
                              "type": "boolean"
                            },
                            "read_early": {
                              "type": "boolean"
                            },
                            "planned_end_from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "4 weeks after the start."
                            },
                            "planned_end_to": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "6 weeks after the start."
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "arm_key": {
                                    "type": "string"
                                  },
                                  "role": {
                                    "type": "string",
                                    "enum": [
                                      "control",
                                      "test"
                                    ]
                                  },
                                  "optimization_event": {
                                    "type": "string",
                                    "enum": [
                                      "conversations",
                                      "first_payment",
                                      "closed_won",
                                      "appointment_attended",
                                      "appointment_booked",
                                      "lead_created"
                                    ],
                                    "description": "What the arm’s ad sets optimise for: `conversations` (Meta’s own «conversaciones», the control) or a stage Vitrina sends to Meta (`first_payment` = «primer pago», `closed_won` = «presupuesto aceptado», `appointment_attended` = «cita atendida»)."
                                  },
                                  "ad_set_count": {
                                    "type": "integer"
                                  },
                                  "learning_status": {
                                    "type": "string",
                                    "enum": [
                                      "learning",
                                      "learning_limited",
                                      "active",
                                      "unknown"
                                    ],
                                    "description": "Meta’s learning stage for the arm’s ad sets, READ from Meta (never guessed): `learning` (Aprendiendo), `learning_limited` (Aprendizaje limitado — too few events to stabilise), `active` (out of learning), `unknown` until a read answers. The least advanced ad set decides."
                                  },
                                  "left_learning_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "spend_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "Spend over the window, CLP; `null` = unknown or not started."
                                  },
                                  "people": {
                                    "type": "integer",
                                    "description": "People whose WhatsApp conversation one of the arm’s ads opened in the window («personas»)."
                                  },
                                  "paying_patients": {
                                    "type": "integer",
                                    "description": "Of them, with a first payment recorded afterwards («pacientes nuevos que pagaron»)."
                                  },
                                  "accepted_quotes": {
                                    "type": "integer"
                                  },
                                  "attended": {
                                    "type": "integer"
                                  },
                                  "cost_per_paying_patient": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "spend / paying_patients, CLP; `null` with none (never 0)."
                                  },
                                  "cost_per_accepted_quote": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "sales_objective": {
                                    "type": [
                                      "boolean",
                                      "null"
                                    ],
                                    "description": "The arm runs under a Sales objective (required to optimise click-to-WhatsApp ads on the first payment); `null` = the engine does not report the objective."
                                  }
                                },
                                "required": [
                                  "arm_key",
                                  "role",
                                  "optimization_event",
                                  "ad_set_count",
                                  "learning_status",
                                  "left_learning_at",
                                  "spend_clp",
                                  "people",
                                  "paying_patients",
                                  "accepted_quotes",
                                  "attended",
                                  "cost_per_paying_patient",
                                  "cost_per_accepted_quote",
                                  "sales_objective"
                                ]
                              }
                            }
                          },
                          "required": [
                            "phase",
                            "from",
                            "to",
                            "days_of_data",
                            "day_number",
                            "planned_days",
                            "learning_finished",
                            "read_early",
                            "planned_end_from",
                            "planned_end_to",
                            "arms"
                          ],
                          "description": "Its readout — spend and cost per paying patient on each side, `day_number` of `planned_days`; `null` when no experiment is open."
                        }
                      },
                      "required": [
                        "experiment",
                        "readout"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "experiment": null,
                    "readout": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/experiments/{id}": {
      "get": {
        "summary": "One optimisation experiment",
        "description": "The experiment with its arms and each arm’s Meta learning stage, read from Meta every 6 hours (`unknown` until Meta answers).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The experiment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsExperiment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                    "name": "Conversaciones vs primer pago",
                    "status": "running",
                    "started_at": "2026-10-02T13:00:00.000Z",
                    "ended_at": null,
                    "readout_from": null,
                    "read_early": false,
                    "created_at": "2026-10-01T21:10:00.000Z",
                    "arms": [
                      {
                        "arm_key": "control",
                        "role": "control",
                        "optimization_event": "conversations",
                        "meta_campaign": null,
                        "meta_ad_sets": [
                          "120211000000000201"
                        ],
                        "budget_share": 50,
                        "learning_status": "active",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": "2026-10-02T18:00:00.000Z"
                      },
                      {
                        "arm_key": "first_payment",
                        "role": "test",
                        "optimization_event": "first_payment",
                        "meta_campaign": "120211000000000300",
                        "meta_ad_sets": [
                          "120211000000000301"
                        ],
                        "budget_share": 50,
                        "learning_status": "learning",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Record an arm’s ad sets, start, end or read early",
        "description": "`arm` records the ad sets (and campaign) an arm runs on, as built in Ads Manager — their learning read restarts. `status: running` once the test arm is live (reads learning right away); `ended` closes it and keeps the record. `read_from` compares from a day before every arm left learning (the readout then says `read_early: true`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "arm": {
                    "type": "object",
                    "properties": {
                      "arm_key": {
                        "type": "string",
                        "pattern": "^[a-z0-9_]{1,40}$"
                      },
                      "ad_sets": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "pattern": "^[0-9]{1,32}$"
                        },
                        "maxItems": 20
                      },
                      "campaign": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "pattern": "^[0-9]{1,32}$"
                      },
                      "budget_share": {
                        "type": [
                          "number",
                          "null"
                        ],
                        "exclusiveMinimum": 0,
                        "maximum": 100
                      }
                    },
                    "required": [
                      "arm_key"
                    ],
                    "additionalProperties": false
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "running",
                      "ended"
                    ],
                    "description": "`running` once the test arm is live in Meta (needs its ad sets); `ended` closes it (the record is kept)."
                  },
                  "read_from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Compare from this clinic day even if an arm is still learning (the readout then says `read_early: true`); `null` = wait for learning."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "arm": {
                  "arm_key": "first_payment",
                  "ad_sets": [
                    "120211000000000301"
                  ],
                  "campaign": "120211000000000300"
                },
                "status": "running"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The experiment after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsExperiment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                    "name": "Conversaciones vs primer pago",
                    "status": "running",
                    "started_at": "2026-10-02T13:00:00.000Z",
                    "ended_at": null,
                    "readout_from": null,
                    "read_early": false,
                    "created_at": "2026-10-01T21:10:00.000Z",
                    "arms": [
                      {
                        "arm_key": "control",
                        "role": "control",
                        "optimization_event": "conversations",
                        "meta_campaign": null,
                        "meta_ad_sets": [
                          "120211000000000201"
                        ],
                        "budget_share": 50,
                        "learning_status": "active",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": "2026-10-02T18:00:00.000Z"
                      },
                      {
                        "arm_key": "first_payment",
                        "role": "test",
                        "optimization_event": "first_payment",
                        "meta_campaign": "120211000000000300",
                        "meta_ad_sets": [
                          "120211000000000301"
                        ],
                        "budget_share": 50,
                        "learning_status": "learning",
                        "learning_checked_at": "2026-10-03T12:00:00.000Z",
                        "left_learning_at": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/experiments/{id}/readout": {
      "get": {
        "summary": "The experiment readout, per arm",
        "description": "Per arm: spend, people whose WhatsApp conversation the arm’s ads opened, «pacientes nuevos que pagaron» (a first payment), accepted presupuestos, and the two costs — cost per paying patient and cost per accepted presupuesto — side by side with the days of data. No winner and no probability. Numbers appear from the day the last arm left Meta’s learning phase (`phase: learning` until then), or from the day the owner chose to read early (`read_early: true`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The readout.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "experiment": {
                          "$ref": "#/components/schemas/AdsExperiment"
                        },
                        "readout": {
                          "type": "object",
                          "properties": {
                            "phase": {
                              "type": "string",
                              "enum": [
                                "setup",
                                "learning",
                                "comparing",
                                "ended"
                              ],
                              "description": "`setup` (test arm not recorded) → `learning` (an arm is still learning; no numbers) → `comparing` → `ended`."
                            },
                            "from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "to": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                            },
                            "days_of_data": {
                              "type": "integer"
                            },
                            "day_number": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "description": "«Día N de 28»: days since the start, 1 on the start day, while the test runs; `null` before it starts and once it ended."
                            },
                            "planned_days": {
                              "type": "integer",
                              "description": "The planned length: 28."
                            },
                            "learning_finished": {
                              "type": "boolean"
                            },
                            "read_early": {
                              "type": "boolean"
                            },
                            "planned_end_from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "4 weeks after the start."
                            },
                            "planned_end_to": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "6 weeks after the start."
                            },
                            "arms": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "arm_key": {
                                    "type": "string"
                                  },
                                  "role": {
                                    "type": "string",
                                    "enum": [
                                      "control",
                                      "test"
                                    ]
                                  },
                                  "optimization_event": {
                                    "type": "string",
                                    "enum": [
                                      "conversations",
                                      "first_payment",
                                      "closed_won",
                                      "appointment_attended",
                                      "appointment_booked",
                                      "lead_created"
                                    ],
                                    "description": "What the arm’s ad sets optimise for: `conversations` (Meta’s own «conversaciones», the control) or a stage Vitrina sends to Meta (`first_payment` = «primer pago», `closed_won` = «presupuesto aceptado», `appointment_attended` = «cita atendida»)."
                                  },
                                  "ad_set_count": {
                                    "type": "integer"
                                  },
                                  "learning_status": {
                                    "type": "string",
                                    "enum": [
                                      "learning",
                                      "learning_limited",
                                      "active",
                                      "unknown"
                                    ],
                                    "description": "Meta’s learning stage for the arm’s ad sets, READ from Meta (never guessed): `learning` (Aprendiendo), `learning_limited` (Aprendizaje limitado — too few events to stabilise), `active` (out of learning), `unknown` until a read answers. The least advanced ad set decides."
                                  },
                                  "left_learning_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "spend_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "Spend over the window, CLP; `null` = unknown or not started."
                                  },
                                  "people": {
                                    "type": "integer",
                                    "description": "People whose WhatsApp conversation one of the arm’s ads opened in the window («personas»)."
                                  },
                                  "paying_patients": {
                                    "type": "integer",
                                    "description": "Of them, with a first payment recorded afterwards («pacientes nuevos que pagaron»)."
                                  },
                                  "accepted_quotes": {
                                    "type": "integer"
                                  },
                                  "attended": {
                                    "type": "integer"
                                  },
                                  "cost_per_paying_patient": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "spend / paying_patients, CLP; `null` with none (never 0)."
                                  },
                                  "cost_per_accepted_quote": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "sales_objective": {
                                    "type": [
                                      "boolean",
                                      "null"
                                    ],
                                    "description": "The arm runs under a Sales objective (required to optimise click-to-WhatsApp ads on the first payment); `null` = the engine does not report the objective."
                                  }
                                },
                                "required": [
                                  "arm_key",
                                  "role",
                                  "optimization_event",
                                  "ad_set_count",
                                  "learning_status",
                                  "left_learning_at",
                                  "spend_clp",
                                  "people",
                                  "paying_patients",
                                  "accepted_quotes",
                                  "attended",
                                  "cost_per_paying_patient",
                                  "cost_per_accepted_quote",
                                  "sales_objective"
                                ]
                              }
                            }
                          },
                          "required": [
                            "phase",
                            "from",
                            "to",
                            "days_of_data",
                            "day_number",
                            "planned_days",
                            "learning_finished",
                            "read_early",
                            "planned_end_from",
                            "planned_end_to",
                            "arms"
                          ]
                        }
                      },
                      "required": [
                        "experiment",
                        "readout"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "experiment": {
                      "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                      "name": "Conversaciones vs primer pago",
                      "status": "running",
                      "started_at": "2026-10-02T13:00:00.000Z",
                      "ended_at": null,
                      "readout_from": null,
                      "read_early": false,
                      "created_at": "2026-10-01T21:10:00.000Z",
                      "arms": [
                        {
                          "arm_key": "control",
                          "role": "control",
                          "optimization_event": "conversations",
                          "meta_campaign": null,
                          "meta_ad_sets": [
                            "120211000000000201"
                          ],
                          "budget_share": 50,
                          "learning_status": "active",
                          "learning_checked_at": "2026-10-03T12:00:00.000Z",
                          "left_learning_at": "2026-10-02T18:00:00.000Z"
                        },
                        {
                          "arm_key": "first_payment",
                          "role": "test",
                          "optimization_event": "first_payment",
                          "meta_campaign": "120211000000000300",
                          "meta_ad_sets": [
                            "120211000000000301"
                          ],
                          "budget_share": 50,
                          "learning_status": "learning",
                          "learning_checked_at": "2026-10-03T12:00:00.000Z",
                          "left_learning_at": null
                        }
                      ]
                    },
                    "readout": {
                      "phase": "comparing",
                      "from": "2026-10-09",
                      "to": "2026-10-30",
                      "days_of_data": 22,
                      "day_number": 29,
                      "planned_days": 28,
                      "learning_finished": true,
                      "read_early": false,
                      "planned_end_from": "2026-10-30",
                      "planned_end_to": "2026-11-13",
                      "arms": [
                        {
                          "arm_key": "control",
                          "role": "control",
                          "optimization_event": "conversations",
                          "ad_set_count": 1,
                          "learning_status": "active",
                          "left_learning_at": "2026-10-02T18:00:00.000Z",
                          "spend_clp": 650000,
                          "people": 210,
                          "paying_patients": 6,
                          "accepted_quotes": 9,
                          "attended": 14,
                          "cost_per_paying_patient": 108333,
                          "cost_per_accepted_quote": 72222,
                          "sales_objective": false
                        },
                        {
                          "arm_key": "first_payment",
                          "role": "test",
                          "optimization_event": "first_payment",
                          "ad_set_count": 1,
                          "learning_status": "learning_limited",
                          "left_learning_at": "2026-10-09T12:00:00.000Z",
                          "spend_clp": 640000,
                          "people": 150,
                          "paying_patients": 8,
                          "accepted_quotes": 10,
                          "attended": 13,
                          "cost_per_paying_patient": 80000,
                          "cost_per_accepted_quote": 64000,
                          "sales_objective": true
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/leadgen-forms": {
      "get": {
        "summary": "Instant Form mappings",
        "description": "The Meta Instant Forms this workspace mapped, and the built-in presets. A form with no entry uses the default mapping (Meta’s standard fields — name, phone, email, company — and every other answer kept in the lead note) and the inbox intake. Submissions arrive once Meta approves lead access for the app.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The forms and presets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "forms": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "form_id": {
                                "type": "string",
                                "description": "The Instant Form's id in Meta."
                              },
                              "meta_form": {
                                "type": "string",
                                "description": "Same value as `form_id` (kept for clients of the `meta_*` name)."
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "intake": {
                                "type": "string",
                                "enum": [
                                  "inbox",
                                  "demo"
                                ]
                              },
                              "enabled": {
                                "type": "boolean"
                              },
                              "mapping": {
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "updated_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "form_id",
                              "meta_form",
                              "name",
                              "intake",
                              "enabled",
                              "mapping",
                              "updated_at"
                            ]
                          }
                        },
                        "presets": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "forms",
                        "presets"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "forms": [
                      {
                        "form_id": "1234567890123456",
                        "meta_form": "1234567890123456",
                        "name": "Vitrina · Automotoras · Higher intent v1",
                        "intake": "demo",
                        "enabled": true,
                        "mapping": {
                          "vertical": "automotora",
                          "rules": []
                        },
                        "updated_at": "2026-10-11T15:00:00.000Z"
                      }
                    ],
                    "presets": [
                      "vitrina_automotora_v1",
                      "vitrina_clinica_v1"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/leadgen-forms/{form_id}": {
      "put": {
        "summary": "Map an Instant Form",
        "description": "Creates or replaces the mapping of one Instant Form: which question is the name, phone, email and company, how fit answers are read, which checkbox is the marketing opt-in and whether contacting the person is allowed by the submission itself (`contact_consent`). `intake: demo` feeds the founders’ demo intake (409 `no_demo_booking` for a workspace that takes none). A missing answer never blocks the lead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,32}$",
              "description": "The Instant Form's id in Meta (`form_id` of the lead)."
            },
            "required": true,
            "description": "The Instant Form's id in Meta (`form_id` of the lead).",
            "name": "form_id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "intake": {
                    "type": "string",
                    "enum": [
                      "inbox",
                      "demo"
                    ],
                    "default": "inbox",
                    "description": "`inbox`: a contact, a lead and an inbox conversation. `demo`: the form feeds the founders’ demo intake (only workspaces that take demo bookings): fit score, Lead / QualifiedLead events and the 7-day follow-up."
                  },
                  "preset": {
                    "type": "string",
                    "enum": [
                      "vitrina_automotora_v1",
                      "vitrina_clinica_v1"
                    ],
                    "description": "A built-in mapping: `vitrina_automotora_v1` (the Vitrina «Automotoras» form) or `vitrina_clinica_v1` (the «Clínicas» form: role, professionals to size, agenda system; vertical `clinica`). Replaces `mapping`."
                  },
                  "mapping": {
                    "type": "object",
                    "properties": {
                      "rules": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "source": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 300
                            },
                            "aliases": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 300
                              },
                              "maxItems": 20
                            },
                            "target": {
                              "type": "string",
                              "enum": [
                                "contact.name",
                                "contact.first_name",
                                "contact.last_name",
                                "contact.phone",
                                "contact.email",
                                "contact.company",
                                "qualification.role",
                                "qualification.size",
                                "qualification.sellers",
                                "qualification.agenda_system",
                                "note"
                              ]
                            },
                            "label": {
                              "type": "string",
                              "maxLength": 200
                            },
                            "values": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "string",
                                "maxLength": 100
                              }
                            }
                          },
                          "required": [
                            "source",
                            "target"
                          ]
                        },
                        "maxItems": 60,
                        "default": []
                      },
                      "marketing_checkbox_key": {
                        "type": "string",
                        "maxLength": 300
                      },
                      "notice_version": {
                        "type": "string",
                        "maxLength": 60
                      },
                      "marketing_text_version": {
                        "type": "string",
                        "maxLength": 60
                      },
                      "contact_consent": {
                        "type": "string",
                        "enum": [
                          "none",
                          "submission",
                          "disclaimer"
                        ]
                      },
                      "vertical": {
                        "type": "string",
                        "enum": [
                          "automotora",
                          "clinica",
                          "otro"
                        ]
                      }
                    },
                    "description": "Per-question rules. A rule names the Meta question (`source`, compared lowercase / without accents / with `_` for spaces; `aliases` add more spellings), where its answer goes (`target`: contact.name, contact.first_name, contact.last_name, contact.phone, contact.email, contact.company, qualification.role, qualification.size, qualification.sellers, qualification.agenda_system, note), and optionally `values` (normalised answer → stored value). Every answer is kept in the lead note whatever its rule; a missing answer never blocks the lead."
                  },
                  "enabled": {
                    "type": "boolean",
                    "default": true
                  }
                }
              },
              "example": {
                "name": "Vitrina · Automotoras · Higher intent v1",
                "intake": "demo",
                "preset": "vitrina_automotora_v1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored mapping.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "form_id": {
                          "type": "string",
                          "description": "The Instant Form's id in Meta."
                        },
                        "meta_form": {
                          "type": "string",
                          "description": "Same value as `form_id` (kept for clients of the `meta_*` name)."
                        },
                        "name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "intake": {
                          "type": "string",
                          "enum": [
                            "inbox",
                            "demo"
                          ]
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "mapping": {
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "updated_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "form_id",
                        "meta_form",
                        "name",
                        "intake",
                        "enabled",
                        "mapping",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "form_id": "1234567890123456",
                    "meta_form": "1234567890123456",
                    "name": "Vitrina · Automotoras · Higher intent v1",
                    "intake": "demo",
                    "enabled": true,
                    "mapping": {
                      "vertical": "automotora",
                      "rules": []
                    },
                    "updated_at": "2026-10-11T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove an Instant Form mapping",
        "description": "Back to the default mapping and the inbox intake for that form. Leads already created are untouched.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{1,32}$",
              "description": "The Instant Form's id in Meta (`form_id` of the lead)."
            },
            "required": true,
            "description": "The Instant Form's id in Meta (`form_id` of the lead).",
            "name": "form_id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ads/leadgen-submissions": {
      "get": {
        "summary": "Latest Instant Form submissions",
        "description": "The latest submissions received, newest first: whether each was applied (`processed`), is being applied (`received`) or failed and will be retried (`failed`, with the error), which intake took it, and the lead and contact it produced. No answers.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The submissions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "submissions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "leadgen_id": {
                                "type": "string",
                                "description": "The submission's id in Meta (`leadgen_id`)."
                              },
                              "meta_leadgen": {
                                "type": "string",
                                "description": "Same value as `leadgen_id` (kept for clients of the `meta_*` name)."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "received",
                                  "processed",
                                  "failed"
                                ]
                              },
                              "route": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "demo",
                                  "inbox",
                                  "inbox_fallback"
                                ]
                              },
                              "attempts": {
                                "type": "integer"
                              },
                              "lead_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "contact_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "leadgen_id",
                              "meta_leadgen",
                              "status",
                              "route",
                              "attempts",
                              "lead_id",
                              "contact_id",
                              "error"
                            ]
                          }
                        }
                      },
                      "required": [
                        "submissions"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "submissions": [
                      {
                        "leadgen_id": "444444444444",
                        "meta_leadgen": "444444444444",
                        "status": "processed",
                        "route": "demo",
                        "attempts": 1,
                        "lead_id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                        "contact_id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e20",
                        "error": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/creative-assets/uploads": {
      "post": {
        "summary": "Start a dealer video upload",
        "description": "Records a dealer video (MP4 or MOV, at most 500 MB) and returns a presigned PUT URL valid 15 minutes. Upload the raw bytes there, then call `complete`. The dealer’s own video always runs uncut, as its own ad (`run_uncut: true`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "mime_type": {
                    "type": "string",
                    "enum": [
                      "video/mp4",
                      "video/quicktime"
                    ],
                    "description": "The video type. MP4 or MOV, what Meta accepts."
                  },
                  "byte_size": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "description": "The file size in bytes (at most 500 MB)."
                  },
                  "title": {
                    "type": "string",
                    "maxLength": 160
                  },
                  "lanzamiento_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The Lanzamiento this video belongs to, when it already exists."
                  }
                },
                "required": [
                  "mime_type",
                  "byte_size"
                ]
              },
              "example": {
                "mime_type": "video/mp4",
                "byte_size": 42000000,
                "title": "Nico en el patio"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The pending video and where to upload it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "asset": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "kind": {
                              "type": "string",
                              "enum": [
                                "dealer_video",
                                "generated_video"
                              ]
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending_upload",
                                "ready",
                                "withdrawn"
                              ]
                            },
                            "lanzamiento_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "title": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "mime_type": {
                              "type": "string"
                            },
                            "byte_size": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "duration_seconds": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "width": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "height": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "aspect": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "9x16",
                                "4x5",
                                "1x1",
                                "16x9",
                                "other"
                              ]
                            },
                            "variant": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "additionalProperties": {},
                              "description": "A generated video: {hook, cut, aspect, voiced}."
                            },
                            "run_uncut": {
                              "type": "boolean",
                              "description": "True: runs exactly as uploaded, as its own ad. Always true for the dealer’s video."
                            },
                            "created_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "kind",
                            "status",
                            "lanzamiento_id",
                            "title",
                            "mime_type",
                            "byte_size",
                            "duration_seconds",
                            "width",
                            "height",
                            "aspect",
                            "variant",
                            "run_uncut",
                            "created_at"
                          ]
                        },
                        "upload": {
                          "type": "object",
                          "properties": {
                            "url": {
                              "type": "string",
                              "format": "uri",
                              "description": "Presigned PUT URL. Upload the raw bytes with the given headers."
                            },
                            "method": {
                              "type": "string",
                              "enum": [
                                "PUT"
                              ]
                            },
                            "headers": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "string"
                              }
                            },
                            "expires_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "url",
                            "method",
                            "headers",
                            "expires_at"
                          ]
                        }
                      },
                      "required": [
                        "asset",
                        "upload"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "asset": {
                      "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                      "kind": "dealer_video",
                      "status": "pending_upload",
                      "lanzamiento_id": null,
                      "title": "Nico en el patio",
                      "mime_type": "video/mp4",
                      "byte_size": null,
                      "duration_seconds": null,
                      "width": null,
                      "height": null,
                      "aspect": null,
                      "variant": null,
                      "run_uncut": true,
                      "created_at": "2026-10-26T15:00:00.000Z"
                    },
                    "upload": {
                      "url": "https://storage.example.com/tenants/…/ads-creatives/01926f3e.mp4?X-Amz-Signature=…",
                      "method": "PUT",
                      "headers": {
                        "content-type": "video/mp4"
                      },
                      "expires_at": "2026-10-26T15:15:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/creative-assets/{id}/complete": {
      "post": {
        "summary": "Finish a dealer video upload",
        "description": "Checks the uploaded object (present, size, type) and marks the video ready. Width and height set its aspect (9x16, 4x5, 1x1, 16x9 or other). 409 `upload_missing` before the bytes arrive. Calling it again is a no-op.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "duration_seconds": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 600
                  },
                  "width": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 8192
                  },
                  "height": {
                    "type": "integer",
                    "exclusiveMinimum": 0,
                    "maximum": 8192
                  }
                }
              },
              "example": {
                "width": 1080,
                "height": 1920,
                "duration_seconds": 23
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The ready video.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "kind": {
                          "type": "string",
                          "enum": [
                            "dealer_video",
                            "generated_video"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "pending_upload",
                            "ready",
                            "withdrawn"
                          ]
                        },
                        "lanzamiento_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "mime_type": {
                          "type": "string"
                        },
                        "byte_size": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "duration_seconds": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "width": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "height": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "aspect": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "9x16",
                            "4x5",
                            "1x1",
                            "16x9",
                            "other"
                          ]
                        },
                        "variant": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {},
                          "description": "A generated video: {hook, cut, aspect, voiced}."
                        },
                        "run_uncut": {
                          "type": "boolean",
                          "description": "True: runs exactly as uploaded, as its own ad. Always true for the dealer’s video."
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "kind",
                        "status",
                        "lanzamiento_id",
                        "title",
                        "mime_type",
                        "byte_size",
                        "duration_seconds",
                        "width",
                        "height",
                        "aspect",
                        "variant",
                        "run_uncut",
                        "created_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                    "kind": "dealer_video",
                    "status": "ready",
                    "lanzamiento_id": null,
                    "title": "Nico en el patio",
                    "mime_type": "video/mp4",
                    "byte_size": 42000000,
                    "duration_seconds": 23,
                    "width": 1080,
                    "height": 1920,
                    "aspect": "9x16",
                    "variant": null,
                    "run_uncut": true,
                    "created_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/creative-assets": {
      "get": {
        "summary": "Lanzamiento videos",
        "description": "The workspace’s Lanzamiento videos, newest first: the dealer’s uploads and the generated consignment videos, each with a playback URL valid 10 minutes. A `withdrawn` video (its presenter consent was revoked) has no playback.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lanzamiento_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "dealer_video",
                "generated_video"
              ]
            },
            "required": false,
            "name": "kind",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The videos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "assets": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "dealer_video",
                                  "generated_video"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "pending_upload",
                                  "ready",
                                  "withdrawn"
                                ]
                              },
                              "lanzamiento_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "title": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "mime_type": {
                                "type": "string"
                              },
                              "byte_size": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "duration_seconds": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "width": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "height": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "aspect": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "9x16",
                                  "4x5",
                                  "1x1",
                                  "16x9",
                                  "other"
                                ]
                              },
                              "variant": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {},
                                "description": "A generated video: {hook, cut, aspect, voiced}."
                              },
                              "run_uncut": {
                                "type": "boolean",
                                "description": "True: runs exactly as uploaded, as its own ad. Always true for the dealer’s video."
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "playback": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "url": {
                                    "type": "string",
                                    "format": "uri"
                                  },
                                  "expires_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "url",
                                  "expires_at"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "kind",
                              "status",
                              "lanzamiento_id",
                              "title",
                              "mime_type",
                              "byte_size",
                              "duration_seconds",
                              "width",
                              "height",
                              "aspect",
                              "variant",
                              "run_uncut",
                              "created_at",
                              "playback"
                            ]
                          }
                        }
                      },
                      "required": [
                        "assets"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "assets": [
                      {
                        "id": "01926f3e-7c1a-7b2e-9f10-3a5b7c9d0e1f",
                        "kind": "dealer_video",
                        "status": "ready",
                        "lanzamiento_id": null,
                        "title": "Nico en el patio",
                        "mime_type": "video/mp4",
                        "byte_size": 42000000,
                        "duration_seconds": 23,
                        "width": 1080,
                        "height": 1920,
                        "aspect": "9x16",
                        "variant": null,
                        "run_uncut": true,
                        "created_at": "2026-10-26T15:00:00.000Z",
                        "playback": {
                          "url": "https://storage.example.com/tenants/…/ads-creatives/01926f3e.mp4?X-Amz-Signature=…",
                          "expires_at": "2026-10-26T15:10:00.000Z"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/creative-assets/{id}": {
      "delete": {
        "summary": "Remove a Lanzamiento video",
        "description": "Deletes the stored video and its record. An ad already running on Meta is not touched.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed."
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ai-agents/{id}/voice-note/preview": {
      "post": {
        "summary": "Hear a test line in the agent’s voice note voice",
        "description": "Synthesizes `text` exactly as a real voice note would be (numbers, prices, times and storage are spoken as words, the alias lexicon applies, ElevenLabs Flash v2.5) and runs the Whisper check. Uses the staged (else live) `behavior_policies.voice_note`, with `voice_id`, `voice_settings` and `lexicon` from the body laid over it, so an unsaved editor value can be heard before saving. Works while `enabled` is false. Counts against the workspace’s daily character cap (`GET /ai-agents/{id}/voice-note/usage`). Always 200: a refusal is `ok: false` with a `code` (`check_failed`, `cap_reached`, `not_configured`, `provider_error`, `invalid_text`) and a `message`. On success `audio_base64` is MP3 (`mime_type`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 800,
                    "description": "The test line, as the agent would write it (numbers, prices and times are normalized to words)."
                  },
                  "voice_id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9]{10,40}$",
                    "description": "Unsaved editor value; defaults to the live (else staged) setting."
                  },
                  "voice_settings": {
                    "type": "object",
                    "properties": {
                      "stability": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1,
                        "description": "0..1. Higher = steadier, less expressive."
                      },
                      "similarity_boost": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      },
                      "speed": {
                        "type": "number",
                        "minimum": 0.7,
                        "maximum": 1.2,
                        "description": "0.7..1.2; 1 = normal pace."
                      },
                      "style": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1
                      },
                      "use_speaker_boost": {
                        "type": "boolean"
                      }
                    },
                    "additionalProperties": false
                  },
                  "lexicon": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "from": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60,
                          "description": "Text as written («GB», «iPhone»)."
                        },
                        "alias": {
                          "type": "string",
                          "maxLength": 120,
                          "description": "What the voice says instead («gigas», «aifón»)."
                        }
                      },
                      "required": [
                        "from",
                        "alias"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 100
                  }
                },
                "required": [
                  "text"
                ],
                "additionalProperties": false
              },
              "example": {
                "text": "Tengo el iPhone 16 Pro Max 256GB en $830.000"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The synthesized line, or the typed refusal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "audio_base64": {
                      "type": "string"
                    },
                    "mime_type": {
                      "type": "string"
                    },
                    "duration_seconds": {
                      "type": "number"
                    },
                    "transcript": {
                      "type": "string",
                      "description": "The original text."
                    },
                    "normalized_text": {
                      "type": "string"
                    },
                    "characters": {
                      "type": "number",
                      "description": "Characters sent to TTS (counted against the daily cap)."
                    },
                    "check": {
                      "type": "object",
                      "properties": {
                        "passed": {
                          "type": "boolean"
                        },
                        "heard": {
                          "type": "string",
                          "description": "What Whisper heard."
                        },
                        "word_error_rate": {
                          "type": "number"
                        },
                        "mismatches": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "original": {
                                "type": "string"
                              },
                              "expected": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "original",
                              "expected"
                            ]
                          }
                        }
                      },
                      "required": [
                        "passed",
                        "heard",
                        "word_error_rate",
                        "mismatches"
                      ]
                    },
                    "code": {
                      "type": "string",
                      "enum": [
                        "check_failed",
                        "cap_reached",
                        "not_configured",
                        "provider_error",
                        "invalid_text"
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "day": {
                          "type": "string",
                          "description": "UTC day, YYYY-MM-DD."
                        },
                        "characters_today": {
                          "type": "number"
                        },
                        "daily_cap": {
                          "type": "number"
                        },
                        "remaining": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "day",
                        "characters_today",
                        "daily_cap",
                        "remaining"
                      ]
                    }
                  },
                  "required": [
                    "ok"
                  ]
                },
                "example": {
                  "data": {
                    "ok": true,
                    "audio_base64": "SUQzBAAAAAAA…",
                    "mime_type": "audio/mpeg",
                    "duration_seconds": 5.2,
                    "transcript": "Tengo el iPhone 16 Pro Max 256GB en $830.000",
                    "normalized_text": "Tengo el iPhone dieciséis Pro Max de doscientos cincuenta y seis en ochocientos treinta mil pesos",
                    "characters": 98,
                    "check": {
                      "passed": true,
                      "heard": "Tengo el iPhone 16 Pro Max de 256 en 830.000 pesos.",
                      "word_error_rate": 0,
                      "mismatches": []
                    },
                    "usage": {
                      "day": "2026-10-28",
                      "characters_today": 412,
                      "daily_cap": 10000,
                      "remaining": 9588
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ai-agents/{id}/voice-note/usage": {
      "get": {
        "summary": "Today’s voice note characters against the daily cap",
        "description": "Characters sent to text-to-speech today (UTC day) by this workspace, previews and agent notes together, and the daily cap. At the cap, synthesis fails closed (`cap_reached`) and the agent sends text.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "AI Agents"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Usage",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "day": {
                      "type": "string",
                      "description": "UTC day, YYYY-MM-DD."
                    },
                    "characters_today": {
                      "type": "number"
                    },
                    "daily_cap": {
                      "type": "number"
                    },
                    "remaining": {
                      "type": "number"
                    }
                  },
                  "required": [
                    "day",
                    "characters_today",
                    "daily_cap",
                    "remaining"
                  ]
                },
                "example": {
                  "data": {
                    "day": "2026-10-28",
                    "characters_today": 412,
                    "daily_cap": 10000,
                    "remaining": 9588
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/creative-sets": {
      "get": {
        "summary": "Lanzamiento creative sets",
        "description": "The latest render sets, newest first, each with its rendered PNGs (angle × format).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20,
              "description": "How many sets, newest first."
            },
            "required": false,
            "description": "How many sets, newest first.",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The sets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sets": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "queued",
                                  "rendering",
                                  "done",
                                  "failed"
                                ]
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "completed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "plan": {
                                "type": "object",
                                "properties": {
                                  "angles": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "angle": {
                                          "type": "string",
                                          "enum": [
                                            "tasacion",
                                            "sin-riesgo",
                                            "pasos",
                                            "fotos",
                                            "comision",
                                            "opciones"
                                          ]
                                        },
                                        "renderable": {
                                          "type": "boolean"
                                        },
                                        "reason": {
                                          "type": "string",
                                          "description": "Why it is not rendered: `needs_test_drives_at_lot`, `needs_photography`, `needs_commission_claim`, `needs_buy_outright`."
                                        },
                                        "creatives": {
                                          "type": "array",
                                          "items": {
                                            "type": "object",
                                            "properties": {
                                              "variant": {
                                                "type": "string"
                                              },
                                              "claims": {
                                                "type": "array",
                                                "items": {
                                                  "type": "string"
                                                }
                                              }
                                            },
                                            "required": [
                                              "variant",
                                              "claims"
                                            ]
                                          }
                                        }
                                      },
                                      "required": [
                                        "angle",
                                        "renderable"
                                      ]
                                    }
                                  },
                                  "shots": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "key": {
                                          "type": "string"
                                        },
                                        "angle": {
                                          "type": "string"
                                        },
                                        "variant": {
                                          "type": "string"
                                        },
                                        "format": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "key",
                                        "angle",
                                        "variant",
                                        "format"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "angles",
                                  "shots"
                                ]
                              },
                              "creatives": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string",
                                      "format": "uuid",
                                      "description": "The creative id. `POST /ads/lanzamientos` takes the 4:5 one as `feed_id` and its 9:16 twin as `vertical_id`."
                                    },
                                    "key": {
                                      "type": "string",
                                      "description": "`<angle>[-<variant>]_<w>x<h>`, unique in the set."
                                    },
                                    "angle": {
                                      "type": "string",
                                      "enum": [
                                        "tasacion",
                                        "sin-riesgo",
                                        "pasos",
                                        "fotos",
                                        "comision",
                                        "opciones"
                                      ]
                                    },
                                    "variant": {
                                      "type": "string",
                                      "enum": [
                                        "default",
                                        "percent",
                                        "only_if_sold",
                                        "min_price"
                                      ]
                                    },
                                    "format": {
                                      "type": "string",
                                      "enum": [
                                        "4x5",
                                        "9x16"
                                      ]
                                    },
                                    "width": {
                                      "type": "integer"
                                    },
                                    "height": {
                                      "type": "integer"
                                    },
                                    "url": {
                                      "type": "string",
                                      "description": "Public PNG URL."
                                    },
                                    "claims": {
                                      "type": "array",
                                      "items": {
                                        "type": "string"
                                      },
                                      "description": "The Oferta / dealer facts this creative states."
                                    }
                                  },
                                  "required": [
                                    "id",
                                    "key",
                                    "angle",
                                    "variant",
                                    "format",
                                    "width",
                                    "height",
                                    "url",
                                    "claims"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "id",
                              "status",
                              "error",
                              "created_at",
                              "completed_at",
                              "plan",
                              "creatives"
                            ]
                          }
                        }
                      },
                      "required": [
                        "sets"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sets": [
                      {
                        "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f11",
                        "status": "done",
                        "error": null,
                        "created_at": "2026-10-26T15:00:00.000Z",
                        "completed_at": "2026-10-26T15:00:21.000Z",
                        "plan": {
                          "angles": [
                            {
                              "angle": "comision",
                              "renderable": true,
                              "creatives": [
                                {
                                  "variant": "percent",
                                  "claims": [
                                    "commission_percent",
                                    "only_pay_if_sold",
                                    "google_rating",
                                    "address"
                                  ]
                                }
                              ]
                            },
                            {
                              "angle": "opciones",
                              "renderable": false,
                              "reason": "needs_buy_outright"
                            }
                          ],
                          "shots": [
                            {
                              "key": "comision-percent_1080x1350",
                              "angle": "comision",
                              "variant": "percent",
                              "format": "4x5"
                            },
                            {
                              "key": "comision-percent_1080x1920",
                              "angle": "comision",
                              "variant": "percent",
                              "format": "9x16"
                            }
                          ]
                        },
                        "creatives": [
                          {
                            "key": "comision-percent_1080x1350",
                            "angle": "comision",
                            "variant": "percent",
                            "format": "4x5",
                            "width": 1080,
                            "height": 1350,
                            "url": "https://media.example.com/<tenant>/other/lanzamiento-<set>-comision-percent_1080x1350.png",
                            "claims": [
                              "commission_percent",
                              "only_pay_if_sold",
                              "google_rating",
                              "address"
                            ]
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Render the Lanzamiento static creatives",
        "description": "Queues a render of the consignment statics (six angles, each at 4:5 1080×1350 and 9:16 1080×1920) from the workspace’s own data: the Branding logo and colour, the Sitio address and Google rating, the connected portals, 1–3 of its own stock photos and the claims of its saved Oferta de consignación (`PUT /ads/consignaciones/oferta`; 409 `oferta_not_configured` until it is set up). An angle the Oferta does not back is not rendered, and `plan.angles[].reason` says why. Answers 202 with the set; poll `GET /ads/lanzamientos/creative-sets/{set_id}` until `status` is `done` or `failed`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "vehicle_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "url": {
                          "type": "string",
                          "format": "uri",
                          "description": "One of that vehicle’s own photo URLs (`vehicle.photos[].url`)."
                        }
                      },
                      "required": [
                        "vehicle_id",
                        "url"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 3,
                    "description": "1–3 of the workspace’s own stock photos, best first. The first one carries «Tu auto, presentado así»."
                  },
                  "angles": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "tasacion",
                        "sin-riesgo",
                        "pasos",
                        "fotos",
                        "comision",
                        "opciones"
                      ]
                    },
                    "minItems": 1,
                    "description": "A subset of angles. Default: every angle the Oferta allows."
                  },
                  "overrides": {
                    "type": "object",
                    "properties": {
                      "address": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200,
                        "description": "Street address for the trust line."
                      },
                      "place": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 60,
                        "description": "Comuna or sector for «En …, con hora agendada»."
                      },
                      "google_rating": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "rating": {
                            "type": "number",
                            "minimum": 1,
                            "maximum": 5
                          },
                          "as_of": {
                            "type": "string",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                          }
                        },
                        "required": [
                          "rating"
                        ]
                      },
                      "portals": {
                        "type": "array",
                        "items": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 40
                        },
                        "maxItems": 4
                      }
                    },
                    "description": "Replace a dealer fact for this set only. `null` hides that line. Default: the Branding / Sitio values and the connected portals."
                  }
                },
                "required": [
                  "photos"
                ]
              },
              "example": {
                "photos": [
                  {
                    "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                    "url": "https://media.example.com/vehicles/<tenant>/photo-1.jpg"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The set, queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "plan": {
                          "type": "object",
                          "properties": {
                            "angles": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "angle": {
                                    "type": "string",
                                    "enum": [
                                      "tasacion",
                                      "sin-riesgo",
                                      "pasos",
                                      "fotos",
                                      "comision",
                                      "opciones"
                                    ]
                                  },
                                  "renderable": {
                                    "type": "boolean"
                                  },
                                  "reason": {
                                    "type": "string",
                                    "description": "Why it is not rendered: `needs_test_drives_at_lot`, `needs_photography`, `needs_commission_claim`, `needs_buy_outright`."
                                  },
                                  "creatives": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "variant": {
                                          "type": "string"
                                        },
                                        "claims": {
                                          "type": "array",
                                          "items": {
                                            "type": "string"
                                          }
                                        }
                                      },
                                      "required": [
                                        "variant",
                                        "claims"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "angle",
                                  "renderable"
                                ]
                              }
                            },
                            "shots": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "angle": {
                                    "type": "string"
                                  },
                                  "variant": {
                                    "type": "string"
                                  },
                                  "format": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "angle",
                                  "variant",
                                  "format"
                                ]
                              }
                            }
                          },
                          "required": [
                            "angles",
                            "shots"
                          ]
                        },
                        "creatives": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The creative id. `POST /ads/lanzamientos` takes the 4:5 one as `feed_id` and its 9:16 twin as `vertical_id`."
                              },
                              "key": {
                                "type": "string",
                                "description": "`<angle>[-<variant>]_<w>x<h>`, unique in the set."
                              },
                              "angle": {
                                "type": "string",
                                "enum": [
                                  "tasacion",
                                  "sin-riesgo",
                                  "pasos",
                                  "fotos",
                                  "comision",
                                  "opciones"
                                ]
                              },
                              "variant": {
                                "type": "string",
                                "enum": [
                                  "default",
                                  "percent",
                                  "only_if_sold",
                                  "min_price"
                                ]
                              },
                              "format": {
                                "type": "string",
                                "enum": [
                                  "4x5",
                                  "9x16"
                                ]
                              },
                              "width": {
                                "type": "integer"
                              },
                              "height": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "description": "Public PNG URL."
                              },
                              "claims": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The Oferta / dealer facts this creative states."
                              }
                            },
                            "required": [
                              "id",
                              "key",
                              "angle",
                              "variant",
                              "format",
                              "width",
                              "height",
                              "url",
                              "claims"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "plan",
                        "creatives"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f11",
                    "status": "queued",
                    "error": null,
                    "created_at": "2026-10-26T15:00:00.000Z",
                    "completed_at": null,
                    "plan": {
                      "angles": [
                        {
                          "angle": "comision",
                          "renderable": true,
                          "creatives": [
                            {
                              "variant": "percent",
                              "claims": [
                                "commission_percent",
                                "only_pay_if_sold",
                                "google_rating",
                                "address"
                              ]
                            }
                          ]
                        },
                        {
                          "angle": "opciones",
                          "renderable": false,
                          "reason": "needs_buy_outright"
                        }
                      ],
                      "shots": [
                        {
                          "key": "comision-percent_1080x1350",
                          "angle": "comision",
                          "variant": "percent",
                          "format": "4x5"
                        },
                        {
                          "key": "comision-percent_1080x1920",
                          "angle": "comision",
                          "variant": "percent",
                          "format": "9x16"
                        }
                      ]
                    },
                    "creatives": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/lanzamientos/creative-sets/{set_id}": {
      "get": {
        "summary": "One Lanzamiento creative set",
        "description": "The set’s state, its plan and the PNGs rendered so far.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "set_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "plan": {
                          "type": "object",
                          "properties": {
                            "angles": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "angle": {
                                    "type": "string",
                                    "enum": [
                                      "tasacion",
                                      "sin-riesgo",
                                      "pasos",
                                      "fotos",
                                      "comision",
                                      "opciones"
                                    ]
                                  },
                                  "renderable": {
                                    "type": "boolean"
                                  },
                                  "reason": {
                                    "type": "string",
                                    "description": "Why it is not rendered: `needs_test_drives_at_lot`, `needs_photography`, `needs_commission_claim`, `needs_buy_outright`."
                                  },
                                  "creatives": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "variant": {
                                          "type": "string"
                                        },
                                        "claims": {
                                          "type": "array",
                                          "items": {
                                            "type": "string"
                                          }
                                        }
                                      },
                                      "required": [
                                        "variant",
                                        "claims"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "angle",
                                  "renderable"
                                ]
                              }
                            },
                            "shots": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "angle": {
                                    "type": "string"
                                  },
                                  "variant": {
                                    "type": "string"
                                  },
                                  "format": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "key",
                                  "angle",
                                  "variant",
                                  "format"
                                ]
                              }
                            }
                          },
                          "required": [
                            "angles",
                            "shots"
                          ]
                        },
                        "creatives": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The creative id. `POST /ads/lanzamientos` takes the 4:5 one as `feed_id` and its 9:16 twin as `vertical_id`."
                              },
                              "key": {
                                "type": "string",
                                "description": "`<angle>[-<variant>]_<w>x<h>`, unique in the set."
                              },
                              "angle": {
                                "type": "string",
                                "enum": [
                                  "tasacion",
                                  "sin-riesgo",
                                  "pasos",
                                  "fotos",
                                  "comision",
                                  "opciones"
                                ]
                              },
                              "variant": {
                                "type": "string",
                                "enum": [
                                  "default",
                                  "percent",
                                  "only_if_sold",
                                  "min_price"
                                ]
                              },
                              "format": {
                                "type": "string",
                                "enum": [
                                  "4x5",
                                  "9x16"
                                ]
                              },
                              "width": {
                                "type": "integer"
                              },
                              "height": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "description": "Public PNG URL."
                              },
                              "claims": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The Oferta / dealer facts this creative states."
                              }
                            },
                            "required": [
                              "id",
                              "key",
                              "angle",
                              "variant",
                              "format",
                              "width",
                              "height",
                              "url",
                              "claims"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "plan",
                        "creatives"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f11",
                    "status": "done",
                    "error": null,
                    "created_at": "2026-10-26T15:00:00.000Z",
                    "completed_at": "2026-10-26T15:00:21.000Z",
                    "plan": {
                      "angles": [
                        {
                          "angle": "comision",
                          "renderable": true,
                          "creatives": [
                            {
                              "variant": "percent",
                              "claims": [
                                "commission_percent",
                                "only_pay_if_sold",
                                "google_rating",
                                "address"
                              ]
                            }
                          ]
                        },
                        {
                          "angle": "opciones",
                          "renderable": false,
                          "reason": "needs_buy_outright"
                        }
                      ],
                      "shots": [
                        {
                          "key": "comision-percent_1080x1350",
                          "angle": "comision",
                          "variant": "percent",
                          "format": "4x5"
                        },
                        {
                          "key": "comision-percent_1080x1920",
                          "angle": "comision",
                          "variant": "percent",
                          "format": "9x16"
                        }
                      ]
                    },
                    "creatives": [
                      {
                        "key": "comision-percent_1080x1350",
                        "angle": "comision",
                        "variant": "percent",
                        "format": "4x5",
                        "width": 1080,
                        "height": 1350,
                        "url": "https://media.example.com/<tenant>/other/lanzamiento-<set>-comision-percent_1080x1350.png",
                        "claims": [
                          "commission_percent",
                          "only_pay_if_sold",
                          "google_rating",
                          "address"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/creative-sets/{set_id}/videos": {
      "get": {
        "summary": "The Lanzamiento consignment videos of a set",
        "description": "Each planned variant with its status (`queued`, `rendering`, `ready`, `failed` with the reason) and, once ready, the creative asset id and a short-lived playback URL. 404 until videos were requested for the set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "set_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The video set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "creative_set_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "planning",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Why the set failed: a missing fact (`address_missing`, `landing_url_missing`, `photos_too_few`), `no_variants`, `every_variant_failed`, or the render service’s reason."
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "templates": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "asi-de-facil",
                              "pov-chat",
                              "tasacion",
                              "m30"
                            ]
                          },
                          "description": "What this set renders."
                        },
                        "voice_skipped": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "M30 hooks rendered music-only because their voice-over cannot fit, and templates not rendered because their voice-over cannot fit 20 s (a long dealer name or landing address)."
                        },
                        "renders": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "render_key": {
                                "type": "string",
                                "description": "`hook<H>-<cut>s-<aspect>-<vo|novo>` (M30) or `tpl-<template>-20s-9x16-vo`, unique in the set."
                              },
                              "template": {
                                "type": "string",
                                "enum": [
                                  "asi-de-facil",
                                  "pov-chat",
                                  "tasacion",
                                  "m30"
                                ],
                                "description": "The template that renders this video (`m30` = the original matrix)."
                              },
                              "hook": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "A",
                                  "B",
                                  "C",
                                  "D"
                                ],
                                "description": "The M30 hook; null for a template video."
                              },
                              "cut": {
                                "type": "integer"
                              },
                              "aspect": {
                                "type": "string",
                                "enum": [
                                  "9x16",
                                  "4x5"
                                ]
                              },
                              "voiced": {
                                "type": "boolean"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "queued",
                                  "rendering",
                                  "ready",
                                  "failed"
                                ]
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Why it failed (failed only)."
                              },
                              "asset_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid",
                                "description": "Once ready: the `generated_video` creative asset. `POST /ads/lanzamientos` takes an M30 4:5 one as `videos[].feed_asset_id` and its 9:16 twin (same hook, cut and voice) as `vertical_asset_id`; a template video (9:16 only) goes alone as `feed_asset_id`."
                              },
                              "playback": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "url": {
                                    "type": "string"
                                  },
                                  "expires_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "url",
                                  "expires_at"
                                ],
                                "description": "A 10-minute playback URL (ready only)."
                              }
                            },
                            "required": [
                              "id",
                              "render_key",
                              "template",
                              "hook",
                              "cut",
                              "aspect",
                              "voiced",
                              "status",
                              "error",
                              "asset_id",
                              "playback"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "creative_set_id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "templates",
                        "voice_skipped",
                        "renders"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "9a3f6c1e-55b2-4f0e-8d7a-1b2c3d4e5f60",
                    "creative_set_id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f11",
                    "status": "rendering",
                    "error": null,
                    "created_at": "2026-10-27T15:00:00.000Z",
                    "completed_at": null,
                    "templates": [
                      "asi-de-facil",
                      "pov-chat",
                      "tasacion"
                    ],
                    "voice_skipped": [],
                    "renders": [
                      {
                        "id": "2b7e1d4a-8c3f-4a6e-9f10-3c4d5e6f7a81",
                        "render_key": "tpl-asi-de-facil-20s-9x16-vo",
                        "template": "asi-de-facil",
                        "hook": null,
                        "cut": 20,
                        "aspect": "9x16",
                        "voiced": true,
                        "status": "ready",
                        "error": null,
                        "asset_id": "2b7e1d4a-8c3f-4a6e-9f10-3c4d5e6f7a81",
                        "playback": {
                          "url": "https://storage.example.com/<tenant>/ads-creatives/<id>.mp4?sig=…",
                          "expires_at": "2026-10-27T15:10:00.000Z"
                        }
                      },
                      {
                        "id": "5d1c8e2f-3a4b-4c5d-8e6f-7a8b9c0d1e22",
                        "render_key": "tpl-pov-chat-20s-9x16-vo",
                        "template": "pov-chat",
                        "hook": null,
                        "cut": 20,
                        "aspect": "9x16",
                        "voiced": true,
                        "status": "rendering",
                        "error": null,
                        "asset_id": null,
                        "playback": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Render the Lanzamiento consignment videos",
        "description": "Queues the generated consignment videos for a render set: the same dealer facts, stock photos and Oferta as its statics, rendered as the templates in `templates` (default: «Así de fácil», «POV: vendes tu auto solo», «¿Cuánto vale tu auto hoy?», one 9:16 voiced video each) and/or `m30`, the original consignment video (hooks × cuts × 9:16/4:5 × voiced/music-only). Answers 202 with the video set; poll `GET …/videos` until `status` is `done` or `failed`. Idempotent: asking again returns the same set and re-queues only the variants that failed. 409 `video_render_not_configured` while the render service is not set up; 409 `oferta_not_configured` when the set’s Oferta cannot back a claim.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "set_id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "templates": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "asi-de-facil",
                        "pov-chat",
                        "tasacion",
                        "m30"
                      ]
                    },
                    "minItems": 1,
                    "description": "Which videos to render. `asi-de-facil` («Así de fácil»: the process as an animated explainer), `pov-chat` («POV: vendes tu auto solo»: a native chat, the chaos of selling alone and the relief), `tasacion` («¿Cuánto vale tu auto hoy?»: the patente, the commission, the floor as figures): one 9:16 voiced video each, ≤ 20 s, copy from the Oferta only. `m30`: the original consignment video (hooks × cuts × 4:5/9:16 × voiced/music-only; `hooks`, `cuts`, `aspects` and `voice` narrow it). Default: the three templates. Asking again with a template the set has not rendered adds it and keeps what is ready."
                  },
                  "hooks": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "A",
                        "B",
                        "C"
                      ]
                    },
                    "minItems": 1,
                    "description": "Hooks to render. Default: every hook the Oferta allows (B needs a commission figure, C needs `segment_label`)."
                  },
                  "cuts": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "12",
                        "16",
                        "20",
                        "24"
                      ]
                    },
                    "minItems": 1,
                    "description": "Lengths in seconds. Default: `20` (the script with the form line needs it; a voiced cut too short for the script is reported as failed with the reason, its music-only twin renders)."
                  },
                  "aspects": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "9x16",
                        "4x5"
                      ]
                    },
                    "minItems": 1,
                    "description": "Default: both. The Lanzamiento runs a 4:5 feed video with its 9:16 twin."
                  },
                  "voice": {
                    "type": "string",
                    "enum": [
                      "both",
                      "voiced",
                      "music"
                    ],
                    "description": "With voice-over, music-only, or both (default; every video also renders music-only)."
                  },
                  "segment_label": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 20,
                    "description": "Hook C («¿Tienes un SUV 2018+?»): the kind of car the dealer is looking for. The year is the Condiciones’ minimum; without both, hook C is not rendered."
                  }
                }
              },
              "example": {
                "templates": [
                  "asi-de-facil",
                  "pov-chat",
                  "tasacion"
                ]
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The video set, queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "creative_set_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "planning",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Why the set failed: a missing fact (`address_missing`, `landing_url_missing`, `photos_too_few`), `no_variants`, `every_variant_failed`, or the render service’s reason."
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "templates": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "asi-de-facil",
                              "pov-chat",
                              "tasacion",
                              "m30"
                            ]
                          },
                          "description": "What this set renders."
                        },
                        "voice_skipped": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "M30 hooks rendered music-only because their voice-over cannot fit, and templates not rendered because their voice-over cannot fit 20 s (a long dealer name or landing address)."
                        },
                        "renders": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "render_key": {
                                "type": "string",
                                "description": "`hook<H>-<cut>s-<aspect>-<vo|novo>` (M30) or `tpl-<template>-20s-9x16-vo`, unique in the set."
                              },
                              "template": {
                                "type": "string",
                                "enum": [
                                  "asi-de-facil",
                                  "pov-chat",
                                  "tasacion",
                                  "m30"
                                ],
                                "description": "The template that renders this video (`m30` = the original matrix)."
                              },
                              "hook": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "A",
                                  "B",
                                  "C",
                                  "D"
                                ],
                                "description": "The M30 hook; null for a template video."
                              },
                              "cut": {
                                "type": "integer"
                              },
                              "aspect": {
                                "type": "string",
                                "enum": [
                                  "9x16",
                                  "4x5"
                                ]
                              },
                              "voiced": {
                                "type": "boolean"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "queued",
                                  "rendering",
                                  "ready",
                                  "failed"
                                ]
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Why it failed (failed only)."
                              },
                              "asset_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid",
                                "description": "Once ready: the `generated_video` creative asset. `POST /ads/lanzamientos` takes an M30 4:5 one as `videos[].feed_asset_id` and its 9:16 twin (same hook, cut and voice) as `vertical_asset_id`; a template video (9:16 only) goes alone as `feed_asset_id`."
                              },
                              "playback": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "url": {
                                    "type": "string"
                                  },
                                  "expires_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "url",
                                  "expires_at"
                                ],
                                "description": "A 10-minute playback URL (ready only)."
                              }
                            },
                            "required": [
                              "id",
                              "render_key",
                              "template",
                              "hook",
                              "cut",
                              "aspect",
                              "voiced",
                              "status",
                              "error",
                              "asset_id",
                              "playback"
                            ]
                          }
                        }
                      },
                      "required": [
                        "id",
                        "creative_set_id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "templates",
                        "voice_skipped",
                        "renders"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "9a3f6c1e-55b2-4f0e-8d7a-1b2c3d4e5f60",
                    "creative_set_id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f11",
                    "status": "queued",
                    "error": null,
                    "created_at": "2026-10-27T15:00:00.000Z",
                    "completed_at": null,
                    "templates": [
                      "asi-de-facil",
                      "pov-chat",
                      "tasacion"
                    ],
                    "voice_skipped": [],
                    "renders": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/car-creative-sets": {
      "get": {
        "summary": "Per-car creative sets",
        "description": "The latest per-car render sets, newest first, optionally for one vehicle, each with its PNGs.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only the sets of this vehicle."
            },
            "required": false,
            "description": "Only the sets of this vehicle.",
            "name": "vehicle_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20,
              "description": "How many sets, newest first."
            },
            "required": false,
            "description": "How many sets, newest first.",
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The sets.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sets": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "plantilla": {
                                "type": "string",
                                "enum": [
                                  "venta_auto"
                                ]
                              },
                              "vehicle_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "queued",
                                  "rendering",
                                  "done",
                                  "failed"
                                ]
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "completed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "vehicle": {
                                "type": "object",
                                "properties": {
                                  "vehicle_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "facts_version": {
                                    "type": "string",
                                    "description": "Fingerprint of the car facts the creatives state. A different value on the live vehicle (e.g. a new price) means: regenerate as a new ad."
                                  },
                                  "price_clp": {
                                    "type": "integer",
                                    "description": "Whole CLP pesos, as printed."
                                  },
                                  "make": {
                                    "type": "string"
                                  },
                                  "model": {
                                    "type": "string"
                                  },
                                  "version": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "year": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ]
                                  },
                                  "odometer_value": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "odometer_unit": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "transmission": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "fuel": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "offers_financing": {
                                    "type": "boolean"
                                  },
                                  "hero_photo": {
                                    "type": "string"
                                  },
                                  "carousel_photos": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "captured_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "vehicle_id",
                                  "facts_version",
                                  "price_clp",
                                  "make",
                                  "model",
                                  "version",
                                  "year",
                                  "odometer_value",
                                  "odometer_unit",
                                  "transmission",
                                  "fuel",
                                  "offers_financing",
                                  "hero_photo",
                                  "carousel_photos",
                                  "captured_at"
                                ]
                              },
                              "plan": {
                                "type": "object",
                                "properties": {
                                  "claims": {
                                    "type": "array",
                                    "items": {
                                      "type": "string"
                                    }
                                  },
                                  "shots": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "key": {
                                          "type": "string"
                                        },
                                        "variant": {
                                          "type": "string",
                                          "enum": [
                                            "hero",
                                            "carousel"
                                          ]
                                        },
                                        "format": {
                                          "type": "string",
                                          "enum": [
                                            "4x5",
                                            "9x16"
                                          ]
                                        },
                                        "card_index": {
                                          "type": [
                                            "integer",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "key",
                                        "variant",
                                        "format",
                                        "card_index"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "claims",
                                  "shots"
                                ]
                              },
                              "creatives": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "key": {
                                      "type": "string",
                                      "description": "`car_<w>x<h>` or `car-card-NN_1080x1350`, unique in the set."
                                    },
                                    "variant": {
                                      "type": "string",
                                      "enum": [
                                        "hero",
                                        "carousel"
                                      ]
                                    },
                                    "format": {
                                      "type": "string",
                                      "enum": [
                                        "4x5",
                                        "9x16"
                                      ]
                                    },
                                    "card_index": {
                                      "type": [
                                        "integer",
                                        "null"
                                      ],
                                      "minimum": 1,
                                      "maximum": 10,
                                      "description": "Carousel position (1..10); null for a static."
                                    },
                                    "width": {
                                      "type": "integer"
                                    },
                                    "height": {
                                      "type": "integer"
                                    },
                                    "url": {
                                      "type": "string",
                                      "description": "Public PNG URL."
                                    },
                                    "claims": {
                                      "type": "array",
                                      "items": {
                                        "type": "string"
                                      },
                                      "description": "The facts this PNG states: price, odometer, transmission, fuel, financing, google_rating, address."
                                    }
                                  },
                                  "required": [
                                    "key",
                                    "variant",
                                    "format",
                                    "card_index",
                                    "width",
                                    "height",
                                    "url",
                                    "claims"
                                  ]
                                },
                                "description": "Statics first (4:5, 9:16), then the carousel cards by `card_index`."
                              }
                            },
                            "required": [
                              "id",
                              "plantilla",
                              "vehicle_id",
                              "status",
                              "error",
                              "created_at",
                              "completed_at",
                              "vehicle",
                              "plan",
                              "creatives"
                            ]
                          }
                        }
                      },
                      "required": [
                        "sets"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sets": [
                      {
                        "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f12",
                        "plantilla": "venta_auto",
                        "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                        "status": "done",
                        "error": null,
                        "created_at": "2026-10-27T15:00:00.000Z",
                        "completed_at": "2026-10-27T15:00:14.000Z",
                        "vehicle": {
                          "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                          "facts_version": "3f9a0c1d2e4b5a6c",
                          "price_clp": 18990000,
                          "make": "Kia",
                          "model": "Sportage",
                          "version": "EX 2.0 4x2 AT",
                          "year": 2023,
                          "odometer_value": 42000,
                          "odometer_unit": "KM",
                          "transmission": "automatica",
                          "fuel": "gasolina",
                          "offers_financing": false,
                          "hero_photo": "https://media.example.com/vehicles/<tenant>/photo-1.jpg",
                          "carousel_photos": [
                            "https://media.example.com/vehicles/<tenant>/photo-1.jpg"
                          ],
                          "captured_at": "2026-10-27T15:00:00.000Z"
                        },
                        "plan": {
                          "claims": [
                            "price",
                            "odometer",
                            "transmission",
                            "fuel",
                            "google_rating"
                          ],
                          "shots": [
                            {
                              "key": "car_1080x1350",
                              "variant": "hero",
                              "format": "4x5",
                              "card_index": null
                            },
                            {
                              "key": "car_1080x1920",
                              "variant": "hero",
                              "format": "9x16",
                              "card_index": null
                            },
                            {
                              "key": "car-card-01_1080x1350",
                              "variant": "carousel",
                              "format": "4x5",
                              "card_index": 1
                            }
                          ]
                        },
                        "creatives": [
                          {
                            "key": "car_1080x1350",
                            "variant": "hero",
                            "format": "4x5",
                            "card_index": null,
                            "width": 1080,
                            "height": 1350,
                            "url": "https://media.example.com/<tenant>/other/lanzamiento-<set>-car_1080x1350.png",
                            "claims": [
                              "price",
                              "odometer",
                              "transmission",
                              "fuel",
                              "google_rating"
                            ]
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Render the creatives of one stock car",
        "description": "Queues a render of one car’s ads for the «Venta de un auto» Plantilla: a 4:5 (1080×1350) and a 9:16 (1080×1920) static, plus a carousel of up to 10 4:5 cards, one per photo, in order. Everything printed comes from the vehicle (make, model, year, version, price, km, transmission, fuel, its own photos) and the workspace (Branding logo and colour, the Sitio Google rating and address, «Financiamiento» only when the dealer offers financing). Nothing is invented: a car with no price answers 409 `needs_price`; a car that is reserved, sold or inactive answers 409 `vehicle_not_available`; one with no photos answers 400. `vehicle` is the snapshot the PNGs state; compare its `price_clp` / `facts_version` with the live vehicle to know when to regenerate. Answers 202; poll `GET /ads/lanzamientos/car-creative-sets/{set_id}` until `status` is `done` or `failed`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "A vehicle of this workspace’s stock."
                  },
                  "hero_photo": {
                    "type": "string",
                    "format": "uri",
                    "description": "The photo of the 4:5 and 9:16 statics. Default: the vehicle’s cover photo (its first, normally the exterior 3/4 front)."
                  },
                  "carousel_photos": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uri",
                      "description": "One of that vehicle’s own photo URLs (`vehicle.photos[].url`)."
                    },
                    "minItems": 1,
                    "maxItems": 10,
                    "description": "The carousel cards, in order (one 4:5 card per photo). Default: the vehicle’s gallery in order, first 10."
                  },
                  "carousel": {
                    "type": "boolean",
                    "description": "`false` renders the two statics only. Default true."
                  },
                  "overrides": {
                    "type": "object",
                    "properties": {
                      "google_rating": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "rating": {
                            "type": "number",
                            "minimum": 1,
                            "maximum": 5
                          },
                          "as_of": {
                            "type": "string",
                            "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                          }
                        },
                        "required": [
                          "rating"
                        ]
                      },
                      "address": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200,
                        "description": "Street address for the trust line; `null` hides it."
                      },
                      "financing": {
                        "type": "boolean",
                        "enum": [
                          false
                        ],
                        "description": "`false` hides the «Financiamiento» chip. It cannot be switched on from here: it shows only when the dealer offers financing (onboarding answer or a financiera on record)."
                      }
                    },
                    "description": "Replace a dealer fact for this set only. `null` hides that line. A request can hide a fact, never add one."
                  }
                },
                "required": [
                  "vehicle_id"
                ]
              },
              "example": {
                "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "The set, queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "venta_auto"
                          ]
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vehicle": {
                          "type": "object",
                          "properties": {
                            "vehicle_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "facts_version": {
                              "type": "string",
                              "description": "Fingerprint of the car facts the creatives state. A different value on the live vehicle (e.g. a new price) means: regenerate as a new ad."
                            },
                            "price_clp": {
                              "type": "integer",
                              "description": "Whole CLP pesos, as printed."
                            },
                            "make": {
                              "type": "string"
                            },
                            "model": {
                              "type": "string"
                            },
                            "version": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "year": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "odometer_value": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "odometer_unit": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "transmission": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "fuel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "offers_financing": {
                              "type": "boolean"
                            },
                            "hero_photo": {
                              "type": "string"
                            },
                            "carousel_photos": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "captured_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "vehicle_id",
                            "facts_version",
                            "price_clp",
                            "make",
                            "model",
                            "version",
                            "year",
                            "odometer_value",
                            "odometer_unit",
                            "transmission",
                            "fuel",
                            "offers_financing",
                            "hero_photo",
                            "carousel_photos",
                            "captured_at"
                          ]
                        },
                        "plan": {
                          "type": "object",
                          "properties": {
                            "claims": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "shots": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "variant": {
                                    "type": "string",
                                    "enum": [
                                      "hero",
                                      "carousel"
                                    ]
                                  },
                                  "format": {
                                    "type": "string",
                                    "enum": [
                                      "4x5",
                                      "9x16"
                                    ]
                                  },
                                  "card_index": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "variant",
                                  "format",
                                  "card_index"
                                ]
                              }
                            }
                          },
                          "required": [
                            "claims",
                            "shots"
                          ]
                        },
                        "creatives": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "description": "`car_<w>x<h>` or `car-card-NN_1080x1350`, unique in the set."
                              },
                              "variant": {
                                "type": "string",
                                "enum": [
                                  "hero",
                                  "carousel"
                                ]
                              },
                              "format": {
                                "type": "string",
                                "enum": [
                                  "4x5",
                                  "9x16"
                                ]
                              },
                              "card_index": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "minimum": 1,
                                "maximum": 10,
                                "description": "Carousel position (1..10); null for a static."
                              },
                              "width": {
                                "type": "integer"
                              },
                              "height": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "description": "Public PNG URL."
                              },
                              "claims": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The facts this PNG states: price, odometer, transmission, fuel, financing, google_rating, address."
                              }
                            },
                            "required": [
                              "key",
                              "variant",
                              "format",
                              "card_index",
                              "width",
                              "height",
                              "url",
                              "claims"
                            ]
                          },
                          "description": "Statics first (4:5, 9:16), then the carousel cards by `card_index`."
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "vehicle_id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "vehicle",
                        "plan",
                        "creatives"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f12",
                    "plantilla": "venta_auto",
                    "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                    "status": "queued",
                    "error": null,
                    "created_at": "2026-10-27T15:00:00.000Z",
                    "completed_at": null,
                    "vehicle": {
                      "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                      "facts_version": "3f9a0c1d2e4b5a6c",
                      "price_clp": 18990000,
                      "make": "Kia",
                      "model": "Sportage",
                      "version": "EX 2.0 4x2 AT",
                      "year": 2023,
                      "odometer_value": 42000,
                      "odometer_unit": "KM",
                      "transmission": "automatica",
                      "fuel": "gasolina",
                      "offers_financing": false,
                      "hero_photo": "https://media.example.com/vehicles/<tenant>/photo-1.jpg",
                      "carousel_photos": [
                        "https://media.example.com/vehicles/<tenant>/photo-1.jpg"
                      ],
                      "captured_at": "2026-10-27T15:00:00.000Z"
                    },
                    "plan": {
                      "claims": [
                        "price",
                        "odometer",
                        "transmission",
                        "fuel",
                        "google_rating"
                      ],
                      "shots": [
                        {
                          "key": "car_1080x1350",
                          "variant": "hero",
                          "format": "4x5",
                          "card_index": null
                        },
                        {
                          "key": "car_1080x1920",
                          "variant": "hero",
                          "format": "9x16",
                          "card_index": null
                        },
                        {
                          "key": "car-card-01_1080x1350",
                          "variant": "carousel",
                          "format": "4x5",
                          "card_index": 1
                        }
                      ]
                    },
                    "creatives": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/lanzamientos/car-creative-sets/{set_id}": {
      "get": {
        "summary": "One per-car creative set",
        "description": "The set’s state, its vehicle snapshot and the PNGs rendered so far.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "set_id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The set.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "venta_auto"
                          ]
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "queued",
                            "rendering",
                            "done",
                            "failed"
                          ]
                        },
                        "error": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "completed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "vehicle": {
                          "type": "object",
                          "properties": {
                            "vehicle_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "facts_version": {
                              "type": "string",
                              "description": "Fingerprint of the car facts the creatives state. A different value on the live vehicle (e.g. a new price) means: regenerate as a new ad."
                            },
                            "price_clp": {
                              "type": "integer",
                              "description": "Whole CLP pesos, as printed."
                            },
                            "make": {
                              "type": "string"
                            },
                            "model": {
                              "type": "string"
                            },
                            "version": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "year": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "odometer_value": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "odometer_unit": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "transmission": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "fuel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "offers_financing": {
                              "type": "boolean"
                            },
                            "hero_photo": {
                              "type": "string"
                            },
                            "carousel_photos": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "captured_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "vehicle_id",
                            "facts_version",
                            "price_clp",
                            "make",
                            "model",
                            "version",
                            "year",
                            "odometer_value",
                            "odometer_unit",
                            "transmission",
                            "fuel",
                            "offers_financing",
                            "hero_photo",
                            "carousel_photos",
                            "captured_at"
                          ]
                        },
                        "plan": {
                          "type": "object",
                          "properties": {
                            "claims": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "shots": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "key": {
                                    "type": "string"
                                  },
                                  "variant": {
                                    "type": "string",
                                    "enum": [
                                      "hero",
                                      "carousel"
                                    ]
                                  },
                                  "format": {
                                    "type": "string",
                                    "enum": [
                                      "4x5",
                                      "9x16"
                                    ]
                                  },
                                  "card_index": {
                                    "type": [
                                      "integer",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "key",
                                  "variant",
                                  "format",
                                  "card_index"
                                ]
                              }
                            }
                          },
                          "required": [
                            "claims",
                            "shots"
                          ]
                        },
                        "creatives": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "description": "`car_<w>x<h>` or `car-card-NN_1080x1350`, unique in the set."
                              },
                              "variant": {
                                "type": "string",
                                "enum": [
                                  "hero",
                                  "carousel"
                                ]
                              },
                              "format": {
                                "type": "string",
                                "enum": [
                                  "4x5",
                                  "9x16"
                                ]
                              },
                              "card_index": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "minimum": 1,
                                "maximum": 10,
                                "description": "Carousel position (1..10); null for a static."
                              },
                              "width": {
                                "type": "integer"
                              },
                              "height": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "description": "Public PNG URL."
                              },
                              "claims": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                },
                                "description": "The facts this PNG states: price, odometer, transmission, fuel, financing, google_rating, address."
                              }
                            },
                            "required": [
                              "key",
                              "variant",
                              "format",
                              "card_index",
                              "width",
                              "height",
                              "url",
                              "claims"
                            ]
                          },
                          "description": "Statics first (4:5, 9:16), then the carousel cards by `card_index`."
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "vehicle_id",
                        "status",
                        "error",
                        "created_at",
                        "completed_at",
                        "vehicle",
                        "plan",
                        "creatives"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "6f1c2a8e-3b7d-4c55-9a10-2d4e8b9c0f12",
                    "plantilla": "venta_auto",
                    "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                    "status": "done",
                    "error": null,
                    "created_at": "2026-10-27T15:00:00.000Z",
                    "completed_at": "2026-10-27T15:00:14.000Z",
                    "vehicle": {
                      "vehicle_id": "01d4822d-dc45-4b42-a78f-a0dfe402ee6e",
                      "facts_version": "3f9a0c1d2e4b5a6c",
                      "price_clp": 18990000,
                      "make": "Kia",
                      "model": "Sportage",
                      "version": "EX 2.0 4x2 AT",
                      "year": 2023,
                      "odometer_value": 42000,
                      "odometer_unit": "KM",
                      "transmission": "automatica",
                      "fuel": "gasolina",
                      "offers_financing": false,
                      "hero_photo": "https://media.example.com/vehicles/<tenant>/photo-1.jpg",
                      "carousel_photos": [
                        "https://media.example.com/vehicles/<tenant>/photo-1.jpg"
                      ],
                      "captured_at": "2026-10-27T15:00:00.000Z"
                    },
                    "plan": {
                      "claims": [
                        "price",
                        "odometer",
                        "transmission",
                        "fuel",
                        "google_rating"
                      ],
                      "shots": [
                        {
                          "key": "car_1080x1350",
                          "variant": "hero",
                          "format": "4x5",
                          "card_index": null
                        },
                        {
                          "key": "car_1080x1920",
                          "variant": "hero",
                          "format": "9x16",
                          "card_index": null
                        },
                        {
                          "key": "car-card-01_1080x1350",
                          "variant": "carousel",
                          "format": "4x5",
                          "card_index": 1
                        }
                      ]
                    },
                    "creatives": [
                      {
                        "key": "car_1080x1350",
                        "variant": "hero",
                        "format": "4x5",
                        "card_index": null,
                        "width": 1080,
                        "height": 1350,
                        "url": "https://media.example.com/<tenant>/other/lanzamiento-<set>-car_1080x1350.png",
                        "claims": [
                          "price",
                          "odometer",
                          "transmission",
                          "fuel",
                          "google_rating"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/conversion-leads": {
      "get": {
        "summary": "Conversion Leads feed",
        "description": "Whether the stages each Instant Form lead reaches in the CRM are sent back to Meta, and the Meta stage name of each. Meta uses them to optimise the form on lead quality instead of form fills. Off until configured; nothing is sent until the workspace has a Conversion Leads dataset in Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The feed configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "dataset": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "missing",
                                "paused",
                                "ready",
                                "unknown"
                              ]
                            },
                            "failed_7d": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "sent_7d": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "failed_7d",
                            "sent_7d"
                          ],
                          "description": "The Conversion Leads dataset in Meta: `missing` = nothing is sent yet; `ready` = stages ship to it; `failed_7d` counts failed deliveries of the last 7 days when the engine reports them."
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "lead_event_source": {
                          "type": "string"
                        },
                        "stage_map": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "stages": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "The CRM stages that can be mapped."
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "dataset",
                        "enabled",
                        "lead_event_source",
                        "stage_map",
                        "stages",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dataset": {
                      "state": "missing",
                      "failed_7d": null,
                      "sent_7d": null
                    },
                    "enabled": true,
                    "lead_event_source": "Vitrina",
                    "stage_map": {
                      "lead_qualified": "qualified",
                      "appointment_booked": "booked",
                      "appointment_attended": "held",
                      "first_payment": "paid"
                    },
                    "stages": [
                      "lead_created",
                      "lead_qualified",
                      "appointment_booked",
                      "appointment_attended",
                      "first_payment"
                    ],
                    "updated_at": "2026-10-11T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Configure the Conversion Leads feed",
        "description": "Replaces the feed configuration: the switch, the CRM name Meta shows and the stage mapping. A stage with no entry is not sent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Send the stages of Instant Form leads to Meta. Nothing is sent until the workspace has a Conversion Leads dataset in Meta."
                  },
                  "lead_event_source": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$",
                    "default": "Vitrina",
                    "description": "The CRM name Meta shows next to the stage events."
                  },
                  "stage_map": {
                    "type": "object",
                    "properties": {
                      "lead_created": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$"
                      },
                      "lead_qualified": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$"
                      },
                      "appointment_booked": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$"
                      },
                      "appointment_attended": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$"
                      },
                      "first_payment": {
                        "type": "string",
                        "pattern": "^[A-Za-z0-9][A-Za-z0-9 _-]{0,63}$"
                      }
                    },
                    "description": "CRM stage -> Meta stage name. Stages: lead_created, lead_qualified, appointment_booked, appointment_attended, first_payment. A stage with no entry is not sent."
                  }
                },
                "required": [
                  "enabled",
                  "stage_map"
                ]
              },
              "example": {
                "enabled": true,
                "lead_event_source": "Vitrina",
                "stage_map": {
                  "lead_qualified": "qualified",
                  "appointment_booked": "booked",
                  "appointment_attended": "held",
                  "first_payment": "paid"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored configuration.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "dataset": {
                          "type": "object",
                          "properties": {
                            "state": {
                              "type": "string",
                              "enum": [
                                "missing",
                                "paused",
                                "ready",
                                "unknown"
                              ]
                            },
                            "failed_7d": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "sent_7d": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "state",
                            "failed_7d",
                            "sent_7d"
                          ],
                          "description": "The Conversion Leads dataset in Meta: `missing` = nothing is sent yet; `ready` = stages ship to it; `failed_7d` counts failed deliveries of the last 7 days when the engine reports them."
                        },
                        "enabled": {
                          "type": "boolean"
                        },
                        "lead_event_source": {
                          "type": "string"
                        },
                        "stage_map": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "stages": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "The CRM stages that can be mapped."
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "dataset",
                        "enabled",
                        "lead_event_source",
                        "stage_map",
                        "stages",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dataset": {
                      "state": "missing",
                      "failed_7d": null,
                      "sent_7d": null
                    },
                    "enabled": true,
                    "lead_event_source": "Vitrina",
                    "stage_map": {
                      "lead_qualified": "qualified",
                      "appointment_booked": "booked",
                      "appointment_attended": "held",
                      "first_payment": "paid"
                    },
                    "stages": [
                      "lead_created",
                      "lead_qualified",
                      "appointment_booked",
                      "appointment_attended",
                      "first_payment"
                    ],
                    "updated_at": "2026-10-11T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/config": {
      "get": {
        "summary": "Consignment capture setup",
        "description": "The dealer setup Lanzamientos are built from: the Oferta de consignación (the claims a creative may say), the Condiciones de captación (the cars the dealer accepts), the Responsables de consignaciones (who receives qualified sellers) and the stock target. Empty until configured.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The setup.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "oferta": {
                          "type": "object",
                          "properties": {
                            "commission_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "exclusiveMinimum": 0,
                              "maximum": 30,
                              "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                            },
                            "commission_plus_iva": {
                              "type": "boolean"
                            },
                            "headlines": {
                              "type": [
                                "array",
                                "null"
                              ],
                              "items": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo"
                                ]
                              },
                              "description": "The headlines the Lanzamiento runs, each as its own ads. null = not set up yet."
                            },
                            "headline_mode": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "commission",
                                "solo_si_vende",
                                "both"
                              ],
                              "description": "Deprecated mirror of headlines: commission, solo_si_vende or both from its commission/solo part; null when headlines is null or only precio_minimo."
                            },
                            "solo_pagas_si_se_vende": {
                              "type": "boolean"
                            },
                            "precio_minimo": {
                              "type": "boolean"
                            },
                            "compra_inmediata": {
                              "type": "boolean"
                            },
                            "test_drives_at_lot": {
                              "type": "boolean"
                            },
                            "transfer_help": {
                              "type": "boolean"
                            },
                            "photography": {
                              "type": "boolean"
                            },
                            "contract_length_days": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cars_sold_per_month": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "commission_pct",
                            "commission_plus_iva",
                            "headlines",
                            "headline_mode",
                            "solo_pagas_si_se_vende",
                            "precio_minimo",
                            "compra_inmediata",
                            "test_drives_at_lot",
                            "transfer_help",
                            "photography",
                            "contract_length_days",
                            "cars_sold_per_month"
                          ],
                          "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
                        },
                        "claims": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo",
                                  "compra_inmediata",
                                  "test_drives_at_lot",
                                  "transfer_help",
                                  "photography",
                                  "contract_length",
                                  "cars_sold_per_month"
                                ]
                              },
                              "text": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "key",
                              "text"
                            ]
                          },
                          "description": "Every claim the Oferta switches on, with default copy. What creatives may say."
                        },
                        "oferta_problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "field": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "field",
                              "message"
                            ]
                          },
                          "description": "What is missing before a Lanzamiento can be built. Empty = ready."
                        },
                        "condiciones": {
                          "type": "object",
                          "properties": {
                            "min_year": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1950,
                              "maximum": 2100
                            },
                            "max_km": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1,
                              "maximum": 2000000
                            },
                            "zone_region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "excluded_makes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "min_year",
                            "max_km",
                            "zone_region_codes",
                            "excluded_makes"
                          ],
                          "description": "The Condiciones de captación. null / empty = no limit on that axis."
                        },
                        "responsables": {
                          "type": "object",
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "As configured."
                            },
                            "effective_user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Who receives qualified sellers now: configured active members, else the designated sellers."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "designated_sellers",
                                "none"
                              ]
                            }
                          },
                          "required": [
                            "user_ids",
                            "effective_user_ids",
                            "source"
                          ]
                        },
                        "stock_target": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "oferta",
                        "claims",
                        "oferta_problems",
                        "condiciones",
                        "responsables",
                        "stock_target",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "oferta": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "claims": [
                      {
                        "key": "commission",
                        "text": "5% de comisión"
                      },
                      {
                        "key": "solo_si_vende",
                        "text": "Solo pagas si se vende"
                      },
                      {
                        "key": "precio_minimo",
                        "text": "Tú pones el precio mínimo"
                      },
                      {
                        "key": "compra_inmediata",
                        "text": "Si prefieres, te lo compramos de inmediato"
                      },
                      {
                        "key": "test_drives_at_lot",
                        "text": "Coordinamos los test drive en nuestro local"
                      },
                      {
                        "key": "transfer_help",
                        "text": "Te ayudamos con la transferencia"
                      },
                      {
                        "key": "photography",
                        "text": "Fotos profesionales de tu auto"
                      },
                      {
                        "key": "contract_length",
                        "text": "Contrato por 60 días"
                      }
                    ],
                    "oferta_problems": [],
                    "condiciones": {
                      "min_year": 2015,
                      "max_km": 150000,
                      "zone_region_codes": [
                        "13"
                      ],
                      "excluded_makes": [
                        "Chery"
                      ]
                    },
                    "responsables": {
                      "user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "effective_user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "source": "configured"
                    },
                    "stock_target": 35,
                    "updated_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/oferta": {
      "put": {
        "summary": "Set the Oferta de consignación",
        "description": "Replaces the Oferta. A headline can only name a claim that is switched on (400 otherwise); creatives, the landing and the video say only what the Oferta switches on.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commission_pct": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "exclusiveMinimum": 0,
                    "maximum": 30,
                    "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                  },
                  "commission_plus_iva": {
                    "type": "boolean",
                    "default": false,
                    "description": "Quote the commission «+ IVA». false = shown without IVA."
                  },
                  "headlines": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "enum": [
                        "commission",
                        "solo_si_vende",
                        "precio_minimo"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 3,
                    "description": "The headlines the Lanzamiento runs, each as its own ads: commission («X% de comisión»), solo_si_vende («Solo pagas si se vende»), precio_minimo («Tú pones el precio mínimo»). null = not set up yet. Send this or the legacy headline_mode; this wins when both are sent."
                  },
                  "headline_mode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "commission",
                      "solo_si_vende",
                      "both"
                    ],
                    "description": "Deprecated, use headlines. commission, solo_si_vende or both (= commission + solo_si_vende). When only this is sent, a precio_minimo headline already stored is kept while precio_minimo stays on."
                  },
                  "solo_pagas_si_se_vende": {
                    "type": "boolean",
                    "default": false
                  },
                  "precio_minimo": {
                    "type": "boolean",
                    "description": "The floor model: the seller names the minimum they want to receive and the dealer keeps what it sells above that. Omitted = unchanged."
                  },
                  "compra_inmediata": {
                    "type": "boolean",
                    "default": false
                  },
                  "test_drives_at_lot": {
                    "type": "boolean",
                    "default": false
                  },
                  "transfer_help": {
                    "type": "boolean",
                    "default": false
                  },
                  "photography": {
                    "type": "boolean",
                    "default": false
                  },
                  "contract_length_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 730,
                    "default": null,
                    "description": "Contract length in days. null = no contract-length claim."
                  },
                  "cars_sold_per_month": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 10000,
                    "default": null,
                    "description": "«Autos vendidos al mes (aprox.)». Optional; the only source of «Más de N autos al mes». Never invented."
                  }
                },
                "required": [
                  "commission_pct"
                ],
                "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
              },
              "example": {
                "commission_pct": 5,
                "commission_plus_iva": false,
                "headlines": [
                  "commission",
                  "precio_minimo"
                ],
                "headline_mode": "commission",
                "solo_pagas_si_se_vende": true,
                "precio_minimo": true,
                "compra_inmediata": true,
                "test_drives_at_lot": true,
                "transfer_help": true,
                "photography": true,
                "contract_length_days": 60,
                "cars_sold_per_month": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole setup after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "oferta": {
                          "type": "object",
                          "properties": {
                            "commission_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "exclusiveMinimum": 0,
                              "maximum": 30,
                              "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                            },
                            "commission_plus_iva": {
                              "type": "boolean"
                            },
                            "headlines": {
                              "type": [
                                "array",
                                "null"
                              ],
                              "items": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo"
                                ]
                              },
                              "description": "The headlines the Lanzamiento runs, each as its own ads. null = not set up yet."
                            },
                            "headline_mode": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "commission",
                                "solo_si_vende",
                                "both"
                              ],
                              "description": "Deprecated mirror of headlines: commission, solo_si_vende or both from its commission/solo part; null when headlines is null or only precio_minimo."
                            },
                            "solo_pagas_si_se_vende": {
                              "type": "boolean"
                            },
                            "precio_minimo": {
                              "type": "boolean"
                            },
                            "compra_inmediata": {
                              "type": "boolean"
                            },
                            "test_drives_at_lot": {
                              "type": "boolean"
                            },
                            "transfer_help": {
                              "type": "boolean"
                            },
                            "photography": {
                              "type": "boolean"
                            },
                            "contract_length_days": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cars_sold_per_month": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "commission_pct",
                            "commission_plus_iva",
                            "headlines",
                            "headline_mode",
                            "solo_pagas_si_se_vende",
                            "precio_minimo",
                            "compra_inmediata",
                            "test_drives_at_lot",
                            "transfer_help",
                            "photography",
                            "contract_length_days",
                            "cars_sold_per_month"
                          ],
                          "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
                        },
                        "claims": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo",
                                  "compra_inmediata",
                                  "test_drives_at_lot",
                                  "transfer_help",
                                  "photography",
                                  "contract_length",
                                  "cars_sold_per_month"
                                ]
                              },
                              "text": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "key",
                              "text"
                            ]
                          },
                          "description": "Every claim the Oferta switches on, with default copy. What creatives may say."
                        },
                        "oferta_problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "field": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "field",
                              "message"
                            ]
                          },
                          "description": "What is missing before a Lanzamiento can be built. Empty = ready."
                        },
                        "condiciones": {
                          "type": "object",
                          "properties": {
                            "min_year": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1950,
                              "maximum": 2100
                            },
                            "max_km": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1,
                              "maximum": 2000000
                            },
                            "zone_region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "excluded_makes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "min_year",
                            "max_km",
                            "zone_region_codes",
                            "excluded_makes"
                          ],
                          "description": "The Condiciones de captación. null / empty = no limit on that axis."
                        },
                        "responsables": {
                          "type": "object",
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "As configured."
                            },
                            "effective_user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Who receives qualified sellers now: configured active members, else the designated sellers."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "designated_sellers",
                                "none"
                              ]
                            }
                          },
                          "required": [
                            "user_ids",
                            "effective_user_ids",
                            "source"
                          ]
                        },
                        "stock_target": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "oferta",
                        "claims",
                        "oferta_problems",
                        "condiciones",
                        "responsables",
                        "stock_target",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "oferta": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "claims": [
                      {
                        "key": "commission",
                        "text": "5% de comisión"
                      },
                      {
                        "key": "solo_si_vende",
                        "text": "Solo pagas si se vende"
                      },
                      {
                        "key": "precio_minimo",
                        "text": "Tú pones el precio mínimo"
                      },
                      {
                        "key": "compra_inmediata",
                        "text": "Si prefieres, te lo compramos de inmediato"
                      },
                      {
                        "key": "test_drives_at_lot",
                        "text": "Coordinamos los test drive en nuestro local"
                      },
                      {
                        "key": "transfer_help",
                        "text": "Te ayudamos con la transferencia"
                      },
                      {
                        "key": "photography",
                        "text": "Fotos profesionales de tu auto"
                      },
                      {
                        "key": "contract_length",
                        "text": "Contrato por 60 días"
                      }
                    ],
                    "oferta_problems": [],
                    "condiciones": {
                      "min_year": 2015,
                      "max_km": 150000,
                      "zone_region_codes": [
                        "13"
                      ],
                      "excluded_makes": [
                        "Chery"
                      ]
                    },
                    "responsables": {
                      "user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "effective_user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "source": "configured"
                    },
                    "stock_target": 35,
                    "updated_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/condiciones": {
      "put": {
        "summary": "Set the Condiciones de captación",
        "description": "Replaces the Condiciones. A lead outside them is marked «fuera de criterio» and never counts as qualified for Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "min_year": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1950,
                    "maximum": 2100
                  },
                  "max_km": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 2000000
                  },
                  "zone_region_codes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(0[1-9]|1[0-6])$"
                    },
                    "maxItems": 16,
                    "default": [],
                    "description": "Regions where the dealer captures cars (CUT codes, «13» = RM). Empty = every region."
                  },
                  "excluded_makes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 60
                    },
                    "maxItems": 100,
                    "default": [],
                    "description": "Makes the dealer does not take. Matched ignoring case, accents and spaces."
                  }
                },
                "required": [
                  "min_year",
                  "max_km"
                ],
                "description": "The Condiciones de captación. null / empty = no limit on that axis."
              },
              "example": {
                "min_year": 2015,
                "max_km": 150000,
                "zone_region_codes": [
                  "13"
                ],
                "excluded_makes": [
                  "Chery"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole setup after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "oferta": {
                          "type": "object",
                          "properties": {
                            "commission_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "exclusiveMinimum": 0,
                              "maximum": 30,
                              "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                            },
                            "commission_plus_iva": {
                              "type": "boolean"
                            },
                            "headlines": {
                              "type": [
                                "array",
                                "null"
                              ],
                              "items": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo"
                                ]
                              },
                              "description": "The headlines the Lanzamiento runs, each as its own ads. null = not set up yet."
                            },
                            "headline_mode": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "commission",
                                "solo_si_vende",
                                "both"
                              ],
                              "description": "Deprecated mirror of headlines: commission, solo_si_vende or both from its commission/solo part; null when headlines is null or only precio_minimo."
                            },
                            "solo_pagas_si_se_vende": {
                              "type": "boolean"
                            },
                            "precio_minimo": {
                              "type": "boolean"
                            },
                            "compra_inmediata": {
                              "type": "boolean"
                            },
                            "test_drives_at_lot": {
                              "type": "boolean"
                            },
                            "transfer_help": {
                              "type": "boolean"
                            },
                            "photography": {
                              "type": "boolean"
                            },
                            "contract_length_days": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cars_sold_per_month": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "commission_pct",
                            "commission_plus_iva",
                            "headlines",
                            "headline_mode",
                            "solo_pagas_si_se_vende",
                            "precio_minimo",
                            "compra_inmediata",
                            "test_drives_at_lot",
                            "transfer_help",
                            "photography",
                            "contract_length_days",
                            "cars_sold_per_month"
                          ],
                          "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
                        },
                        "claims": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo",
                                  "compra_inmediata",
                                  "test_drives_at_lot",
                                  "transfer_help",
                                  "photography",
                                  "contract_length",
                                  "cars_sold_per_month"
                                ]
                              },
                              "text": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "key",
                              "text"
                            ]
                          },
                          "description": "Every claim the Oferta switches on, with default copy. What creatives may say."
                        },
                        "oferta_problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "field": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "field",
                              "message"
                            ]
                          },
                          "description": "What is missing before a Lanzamiento can be built. Empty = ready."
                        },
                        "condiciones": {
                          "type": "object",
                          "properties": {
                            "min_year": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1950,
                              "maximum": 2100
                            },
                            "max_km": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1,
                              "maximum": 2000000
                            },
                            "zone_region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "excluded_makes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "min_year",
                            "max_km",
                            "zone_region_codes",
                            "excluded_makes"
                          ],
                          "description": "The Condiciones de captación. null / empty = no limit on that axis."
                        },
                        "responsables": {
                          "type": "object",
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "As configured."
                            },
                            "effective_user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Who receives qualified sellers now: configured active members, else the designated sellers."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "designated_sellers",
                                "none"
                              ]
                            }
                          },
                          "required": [
                            "user_ids",
                            "effective_user_ids",
                            "source"
                          ]
                        },
                        "stock_target": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "oferta",
                        "claims",
                        "oferta_problems",
                        "condiciones",
                        "responsables",
                        "stock_target",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "oferta": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "claims": [
                      {
                        "key": "commission",
                        "text": "5% de comisión"
                      },
                      {
                        "key": "solo_si_vende",
                        "text": "Solo pagas si se vende"
                      },
                      {
                        "key": "precio_minimo",
                        "text": "Tú pones el precio mínimo"
                      },
                      {
                        "key": "compra_inmediata",
                        "text": "Si prefieres, te lo compramos de inmediato"
                      },
                      {
                        "key": "test_drives_at_lot",
                        "text": "Coordinamos los test drive en nuestro local"
                      },
                      {
                        "key": "transfer_help",
                        "text": "Te ayudamos con la transferencia"
                      },
                      {
                        "key": "photography",
                        "text": "Fotos profesionales de tu auto"
                      },
                      {
                        "key": "contract_length",
                        "text": "Contrato por 60 días"
                      }
                    ],
                    "oferta_problems": [],
                    "condiciones": {
                      "min_year": 2015,
                      "max_km": 150000,
                      "zone_region_codes": [
                        "13"
                      ],
                      "excluded_makes": [
                        "Chery"
                      ]
                    },
                    "responsables": {
                      "user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "effective_user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "source": "configured"
                    },
                    "stock_target": 35,
                    "updated_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/responsables": {
      "put": {
        "summary": "Set the Responsables de consignaciones",
        "description": "Replaces the members who receive qualified sellers. Only active members (400 otherwise). Empty = the designated sellers receive them.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 50,
                    "description": "Workspace members who receive qualified sellers. Empty = the designated sellers receive them."
                  }
                },
                "required": [
                  "user_ids"
                ]
              },
              "example": {
                "user_ids": [
                  "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole setup after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "oferta": {
                          "type": "object",
                          "properties": {
                            "commission_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "exclusiveMinimum": 0,
                              "maximum": 30,
                              "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                            },
                            "commission_plus_iva": {
                              "type": "boolean"
                            },
                            "headlines": {
                              "type": [
                                "array",
                                "null"
                              ],
                              "items": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo"
                                ]
                              },
                              "description": "The headlines the Lanzamiento runs, each as its own ads. null = not set up yet."
                            },
                            "headline_mode": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "commission",
                                "solo_si_vende",
                                "both"
                              ],
                              "description": "Deprecated mirror of headlines: commission, solo_si_vende or both from its commission/solo part; null when headlines is null or only precio_minimo."
                            },
                            "solo_pagas_si_se_vende": {
                              "type": "boolean"
                            },
                            "precio_minimo": {
                              "type": "boolean"
                            },
                            "compra_inmediata": {
                              "type": "boolean"
                            },
                            "test_drives_at_lot": {
                              "type": "boolean"
                            },
                            "transfer_help": {
                              "type": "boolean"
                            },
                            "photography": {
                              "type": "boolean"
                            },
                            "contract_length_days": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cars_sold_per_month": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "commission_pct",
                            "commission_plus_iva",
                            "headlines",
                            "headline_mode",
                            "solo_pagas_si_se_vende",
                            "precio_minimo",
                            "compra_inmediata",
                            "test_drives_at_lot",
                            "transfer_help",
                            "photography",
                            "contract_length_days",
                            "cars_sold_per_month"
                          ],
                          "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
                        },
                        "claims": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo",
                                  "compra_inmediata",
                                  "test_drives_at_lot",
                                  "transfer_help",
                                  "photography",
                                  "contract_length",
                                  "cars_sold_per_month"
                                ]
                              },
                              "text": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "key",
                              "text"
                            ]
                          },
                          "description": "Every claim the Oferta switches on, with default copy. What creatives may say."
                        },
                        "oferta_problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "field": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "field",
                              "message"
                            ]
                          },
                          "description": "What is missing before a Lanzamiento can be built. Empty = ready."
                        },
                        "condiciones": {
                          "type": "object",
                          "properties": {
                            "min_year": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1950,
                              "maximum": 2100
                            },
                            "max_km": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1,
                              "maximum": 2000000
                            },
                            "zone_region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "excluded_makes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "min_year",
                            "max_km",
                            "zone_region_codes",
                            "excluded_makes"
                          ],
                          "description": "The Condiciones de captación. null / empty = no limit on that axis."
                        },
                        "responsables": {
                          "type": "object",
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "As configured."
                            },
                            "effective_user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Who receives qualified sellers now: configured active members, else the designated sellers."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "designated_sellers",
                                "none"
                              ]
                            }
                          },
                          "required": [
                            "user_ids",
                            "effective_user_ids",
                            "source"
                          ]
                        },
                        "stock_target": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "oferta",
                        "claims",
                        "oferta_problems",
                        "condiciones",
                        "responsables",
                        "stock_target",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "oferta": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "claims": [
                      {
                        "key": "commission",
                        "text": "5% de comisión"
                      },
                      {
                        "key": "solo_si_vende",
                        "text": "Solo pagas si se vende"
                      },
                      {
                        "key": "precio_minimo",
                        "text": "Tú pones el precio mínimo"
                      },
                      {
                        "key": "compra_inmediata",
                        "text": "Si prefieres, te lo compramos de inmediato"
                      },
                      {
                        "key": "test_drives_at_lot",
                        "text": "Coordinamos los test drive en nuestro local"
                      },
                      {
                        "key": "transfer_help",
                        "text": "Te ayudamos con la transferencia"
                      },
                      {
                        "key": "photography",
                        "text": "Fotos profesionales de tu auto"
                      },
                      {
                        "key": "contract_length",
                        "text": "Contrato por 60 días"
                      }
                    ],
                    "oferta_problems": [],
                    "condiciones": {
                      "min_year": 2015,
                      "max_km": 150000,
                      "zone_region_codes": [
                        "13"
                      ],
                      "excluded_makes": [
                        "Chery"
                      ]
                    },
                    "responsables": {
                      "user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "effective_user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "source": "configured"
                    },
                    "stock_target": 35,
                    "updated_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/stock-target": {
      "put": {
        "summary": "Set the stock target",
        "description": "The number of cars in stock the dealer aims for. Shown with progress; Vitrina suggests lowering the budget once reached and never pauses anything by itself. null clears it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "stock_target": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 10000,
                    "description": "Cars in stock the dealer aims for. Shown with progress; nothing pauses when reached."
                  }
                },
                "required": [
                  "stock_target"
                ]
              },
              "example": {
                "stock_target": 35
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole setup after the change.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "oferta": {
                          "type": "object",
                          "properties": {
                            "commission_pct": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "exclusiveMinimum": 0,
                              "maximum": 30,
                              "description": "Commission in percentage points (5 = 5%). null = the commission claim is off."
                            },
                            "commission_plus_iva": {
                              "type": "boolean"
                            },
                            "headlines": {
                              "type": [
                                "array",
                                "null"
                              ],
                              "items": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo"
                                ]
                              },
                              "description": "The headlines the Lanzamiento runs, each as its own ads. null = not set up yet."
                            },
                            "headline_mode": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "commission",
                                "solo_si_vende",
                                "both"
                              ],
                              "description": "Deprecated mirror of headlines: commission, solo_si_vende or both from its commission/solo part; null when headlines is null or only precio_minimo."
                            },
                            "solo_pagas_si_se_vende": {
                              "type": "boolean"
                            },
                            "precio_minimo": {
                              "type": "boolean"
                            },
                            "compra_inmediata": {
                              "type": "boolean"
                            },
                            "test_drives_at_lot": {
                              "type": "boolean"
                            },
                            "transfer_help": {
                              "type": "boolean"
                            },
                            "photography": {
                              "type": "boolean"
                            },
                            "contract_length_days": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "cars_sold_per_month": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "commission_pct",
                            "commission_plus_iva",
                            "headlines",
                            "headline_mode",
                            "solo_pagas_si_se_vende",
                            "precio_minimo",
                            "compra_inmediata",
                            "test_drives_at_lot",
                            "transfer_help",
                            "photography",
                            "contract_length_days",
                            "cars_sold_per_month"
                          ],
                          "description": "The Oferta de consignación: every claim is a switch. A creative can only say what the Oferta switches on."
                        },
                        "claims": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "commission",
                                  "solo_si_vende",
                                  "precio_minimo",
                                  "compra_inmediata",
                                  "test_drives_at_lot",
                                  "transfer_help",
                                  "photography",
                                  "contract_length",
                                  "cars_sold_per_month"
                                ]
                              },
                              "text": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "key",
                              "text"
                            ]
                          },
                          "description": "Every claim the Oferta switches on, with default copy. What creatives may say."
                        },
                        "oferta_problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "field": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "field",
                              "message"
                            ]
                          },
                          "description": "What is missing before a Lanzamiento can be built. Empty = ready."
                        },
                        "condiciones": {
                          "type": "object",
                          "properties": {
                            "min_year": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1950,
                              "maximum": 2100
                            },
                            "max_km": {
                              "type": [
                                "integer",
                                "null"
                              ],
                              "minimum": 1,
                              "maximum": 2000000
                            },
                            "zone_region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "excluded_makes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            }
                          },
                          "required": [
                            "min_year",
                            "max_km",
                            "zone_region_codes",
                            "excluded_makes"
                          ],
                          "description": "The Condiciones de captación. null / empty = no limit on that axis."
                        },
                        "responsables": {
                          "type": "object",
                          "properties": {
                            "user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "As configured."
                            },
                            "effective_user_ids": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "description": "Who receives qualified sellers now: configured active members, else the designated sellers."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "configured",
                                "designated_sellers",
                                "none"
                              ]
                            }
                          },
                          "required": [
                            "user_ids",
                            "effective_user_ids",
                            "source"
                          ]
                        },
                        "stock_target": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "updated_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "oferta",
                        "claims",
                        "oferta_problems",
                        "condiciones",
                        "responsables",
                        "stock_target",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "oferta": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "claims": [
                      {
                        "key": "commission",
                        "text": "5% de comisión"
                      },
                      {
                        "key": "solo_si_vende",
                        "text": "Solo pagas si se vende"
                      },
                      {
                        "key": "precio_minimo",
                        "text": "Tú pones el precio mínimo"
                      },
                      {
                        "key": "compra_inmediata",
                        "text": "Si prefieres, te lo compramos de inmediato"
                      },
                      {
                        "key": "test_drives_at_lot",
                        "text": "Coordinamos los test drive en nuestro local"
                      },
                      {
                        "key": "transfer_help",
                        "text": "Te ayudamos con la transferencia"
                      },
                      {
                        "key": "photography",
                        "text": "Fotos profesionales de tu auto"
                      },
                      {
                        "key": "contract_length",
                        "text": "Contrato por 60 días"
                      }
                    ],
                    "oferta_problems": [],
                    "condiciones": {
                      "min_year": 2015,
                      "max_km": 150000,
                      "zone_region_codes": [
                        "13"
                      ],
                      "excluded_makes": [
                        "Chery"
                      ]
                    },
                    "responsables": {
                      "user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "effective_user_ids": [
                        "8f7c1c1e-2a5b-4c1e-9d10-0b2f3a4c5d6e"
                      ],
                      "source": "configured"
                    },
                    "stock_target": 35,
                    "updated_at": "2026-10-26T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/landing": {
      "get": {
        "summary": "The «Vende tu auto» landing",
        "description": "Whether the consignment landing is on and where the landing lane points: the Sitio page (https://<host>/vende-tu-auto) for a dealer with an active Sitio, else the dealer's own page carrying the embed snippet. Its copy comes only from the Oferta de consignación.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The landing switch and destination.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VendeTuAutoLandingDestination"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "enabled_at": "2026-10-26T15:00:00.000Z",
                    "enabled_source": "lanzamiento",
                    "url": "https://upautos.cl/vende-tu-auto",
                    "kind": "sitio",
                    "external_url": null,
                    "embed_snippet": "<div id=\"vitrina-vende-tu-auto\"></div>\n<script src=\"https://api.vitrinadev.com/vende-tu-auto.js\"\n        data-key=\"pk_live_…\"\n        data-container=\"#vitrina-vende-tu-auto\" async></script>"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Switch the «Vende tu auto» landing",
        "description": "Turns the consignment landing on or off and sets the dealer’s own page for the embed. A Lanzamiento with a landing lane turns it on by itself.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean",
                    "description": "Switch the «Vende tu auto» landing on (the Sitio page and the embed render) or off."
                  },
                  "external_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2048,
                    "format": "uri",
                    "description": "The dealer's OWN https page carrying the embed, for a dealer without a Vitrina Sitio. Omit to keep, null to clear."
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The landing switch and destination.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VendeTuAutoLandingDestination"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "enabled_at": "2026-10-26T15:00:00.000Z",
                    "enabled_source": "lanzamiento",
                    "url": "https://upautos.cl/vende-tu-auto",
                    "kind": "sitio",
                    "external_url": null,
                    "embed_snippet": "<div id=\"vitrina-vende-tu-auto\"></div>\n<script src=\"https://api.vitrinadev.com/vende-tu-auto.js\"\n        data-key=\"pk_live_…\"\n        data-container=\"#vitrina-vende-tu-auto\" async></script>"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos": {
      "get": {
        "summary": "Lanzamientos",
        "description": "The Lanzamientos of the workspace, newest first: each is the set of Meta campaigns Vitrina created on the ad account from one Plantilla, with its lanes and status. The first Lanzamiento of a dealer waits for a Vitrina review before it can go live.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "building",
                "paused_ready",
                "in_review",
                "live",
                "failed",
                "rolled_back"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "captacion_consignaciones",
                "venta_auto"
              ]
            },
            "required": false,
            "name": "plantilla",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The Lanzamientos.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "plantilla": {
                            "type": "string",
                            "enum": [
                              "captacion_consignaciones",
                              "venta_auto"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "building",
                              "paused_ready",
                              "in_review",
                              "live",
                              "failed",
                              "rolled_back"
                            ]
                          },
                          "requires_review": {
                            "type": "boolean",
                            "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                          },
                          "reviewed_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "budget_package": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "daily_budget_clp": {
                            "type": [
                              "number",
                              "null"
                            ]
                          },
                          "geo": {
                            "type": "object",
                            "additionalProperties": {}
                          },
                          "status_changed_at": {
                            "type": "string"
                          },
                          "launched_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "vehicle_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                          },
                          "stock_paused_at": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                          },
                          "stock_pause_reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "enum": [
                              "sold",
                              "reserved",
                              "inactive",
                              "deleted"
                            ]
                          },
                          "lanes": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "id": {
                                  "type": "string"
                                },
                                "lane": {
                                  "type": "string",
                                  "enum": [
                                    "landing",
                                    "whatsapp"
                                  ]
                                },
                                "daily_budget_clp": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "destination_url": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "messaging_account_id": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "optimization_goal": {
                                  "type": "string",
                                  "enum": [
                                    "lead",
                                    "conversation",
                                    "qualified_seller"
                                  ]
                                },
                                "ladder": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "status": {
                                      "type": "string",
                                      "enum": [
                                        "waiting",
                                        "skipped",
                                        "stepping",
                                        "stepped",
                                        "failed"
                                      ],
                                      "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                    },
                                    "reason": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "reason_label": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "Dealer-facing sentence (es-CL) for `reason`."
                                    },
                                    "qualified_7d": {
                                      "type": [
                                        "number",
                                        "null"
                                      ]
                                    },
                                    "threshold": {
                                      "type": "number"
                                    },
                                    "campaign_age_days": {
                                      "type": [
                                        "number",
                                        "null"
                                      ]
                                    },
                                    "min_campaign_age_days": {
                                      "type": "number"
                                    },
                                    "checked_at": {
                                      "type": "string"
                                    }
                                  },
                                  "required": [
                                    "status",
                                    "reason",
                                    "reason_label",
                                    "qualified_7d",
                                    "threshold",
                                    "campaign_age_days",
                                    "min_campaign_age_days",
                                    "checked_at"
                                  ],
                                  "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                                },
                                "creative_mix": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "enum": [
                                    "mixed",
                                    "separate"
                                  ],
                                  "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                                },
                                "build_failure": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "code": {
                                      "type": "string"
                                    },
                                    "message": {
                                      "type": "string",
                                      "description": "What happened, in plain words for the dealer."
                                    },
                                    "meta_message": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                    }
                                  },
                                  "required": [
                                    "code",
                                    "message"
                                  ],
                                  "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                                }
                              },
                              "required": [
                                "id",
                                "lane",
                                "daily_budget_clp",
                                "destination_url",
                                "messaging_account_id",
                                "optimization_goal",
                                "ladder",
                                "creative_mix",
                                "build_failure"
                              ]
                            }
                          }
                        },
                        "required": [
                          "id",
                          "plantilla",
                          "status",
                          "requires_review",
                          "reviewed_at",
                          "budget_package",
                          "daily_budget_clp",
                          "geo",
                          "status_changed_at",
                          "launched_at",
                          "created_at",
                          "vehicle_id",
                          "stock_paused_at",
                          "stock_pause_reason",
                          "lanes"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                      "plantilla": "captacion_consignaciones",
                      "status": "paused_ready",
                      "requires_review": true,
                      "reviewed_at": null,
                      "budget_package": "recomendado",
                      "daily_budget_clp": 60000,
                      "geo": {
                        "region_codes": [
                          "13"
                        ],
                        "location_types": [
                          "home"
                        ]
                      },
                      "status_changed_at": "2026-10-26T15:10:00.000Z",
                      "launched_at": null,
                      "vehicle_id": null,
                      "stock_paused_at": null,
                      "stock_pause_reason": null,
                      "created_at": "2026-10-26T15:00:00.000Z",
                      "lanes": [
                        {
                          "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                          "lane": "landing",
                          "daily_budget_clp": 30000,
                          "destination_url": "https://upautos.cl/vende-tu-auto",
                          "messaging_account_id": null,
                          "optimization_goal": "lead",
                          "ladder": null
                        },
                        {
                          "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f602",
                          "lane": "whatsapp",
                          "daily_budget_clp": 30000,
                          "destination_url": null,
                          "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                          "optimization_goal": "conversation",
                          "ladder": null
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a Lanzamiento",
        "description": "Creates the Lanzamiento from the creatives the dealer picked (by id: 4:5 + 9:16 static pairs, optional videos) and starts building it on Meta in the background: per lane one ABO testing campaign, a statics ad set (plus a video ad set when a video was picked), one ad per angle (both headline modes as separate ads when the Oferta runs both) and an empty paused CBO Winners campaign. EVERYTHING IS CREATED PAUSED. Answers 202 with status building; poll GET /ads/lanzamientos/{id} until paused_ready or failed (a failed build is rolled back and carries a failure). 400 with problems when it cannot be built.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "budget": {
                    "type": "object",
                    "properties": {
                      "package": {
                        "type": "string",
                        "enum": [
                          "inicial",
                          "recomendado",
                          "intensivo",
                          "custom"
                        ]
                      },
                      "custom_daily_total_clp": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 40000,
                        "maximum": 10000000,
                        "description": "Required for `custom`: the daily total across both lanes, CLP."
                      }
                    },
                    "required": [
                      "package"
                    ],
                    "description": "Daily total across lanes: Inicial $40k, Recomendado $60k, Intensivo $100k, or custom ≥ $40k. Split evenly across the ad sets of every lane, ≥ $20k per ad set."
                  },
                  "lanes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "lane": {
                          "type": "string",
                          "enum": [
                            "landing",
                            "whatsapp"
                          ]
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "whatsapp: the connected WhatsApp the ads open."
                        }
                      },
                      "required": [
                        "lane"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 2
                  },
                  "creative_mix": {
                    "type": "string",
                    "enum": [
                      "mixed",
                      "separate"
                    ],
                    "description": "How videos sit next to statics on every lane. `mixed`: ONE ad set per lane with the videos as extra ads beside the statics (they compete; Meta delivers more of whichever works). `separate`: a video ad set per lane next to the statics one (two ad sets per lane, ≥ $20k each). Omitted: `separate` when the budget funds ≥ $20k per separate ad set, else `mixed`. The preflight's `budget.creative_mix` says which one applies."
                  },
                  "region_codes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(0?[1-9]|1[0-6])$"
                    },
                    "maxItems": 16,
                    "description": "CUT region codes; omitted = the dealer's own region."
                  },
                  "page_id": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "dataset_id": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "creatives": {
                    "type": "object",
                    "properties": {
                      "statics": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "feed_id": {
                              "type": "string",
                              "format": "uuid",
                              "description": "A 4:5 `lanzamiento_creative` id (#5878)."
                            },
                            "vertical_id": {
                              "type": "string",
                              "format": "uuid",
                              "description": "The 9:16 render of the same angle and variant."
                            }
                          },
                          "required": [
                            "feed_id",
                            "vertical_id"
                          ]
                        },
                        "maxItems": 12,
                        "default": []
                      },
                      "videos": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "feed_asset_id": {
                              "type": "string",
                              "format": "uuid",
                              "description": "A ready `lanzamiento_creative_asset` id (#5879): a generated 4:5 or the dealer's own video."
                            },
                            "vertical_asset_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid",
                              "description": "The 9:16 sibling of a generated video. Ignored for a dealer's own video (runs uncut)."
                            }
                          },
                          "required": [
                            "feed_asset_id"
                          ]
                        },
                        "maxItems": 4,
                        "default": []
                      },
                      "thumbnail_creative_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "format": "uuid",
                        "description": "The image a video shows before it plays. Default: the first static's 4:5."
                      }
                    }
                  }
                },
                "required": [
                  "budget",
                  "lanes",
                  "creatives"
                ]
              },
              "example": {
                "budget": {
                  "package": "recomendado"
                },
                "lanes": [
                  {
                    "lane": "landing"
                  },
                  {
                    "lane": "whatsapp",
                    "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5"
                  }
                ],
                "creatives": {
                  "statics": [
                    {
                      "feed_id": "3f0c9a1e-1b2c-4d3e-8f40-5a6b7c8d9e01",
                      "vertical_id": "3f0c9a1e-1b2c-4d3e-8f40-5a6b7c8d9e02"
                    }
                  ],
                  "videos": []
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Created; the build runs in the background.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "captacion_consignaciones",
                            "venta_auto"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "building",
                            "paused_ready",
                            "in_review",
                            "live",
                            "failed",
                            "rolled_back"
                          ]
                        },
                        "requires_review": {
                          "type": "boolean",
                          "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                        },
                        "reviewed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "budget_package": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "daily_budget_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "status_changed_at": {
                          "type": "string"
                        },
                        "launched_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                        },
                        "stock_paused_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                        },
                        "stock_pause_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "sold",
                            "reserved",
                            "inactive",
                            "deleted"
                          ]
                        },
                        "lanes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane": {
                                "type": "string",
                                "enum": [
                                  "landing",
                                  "whatsapp"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "destination_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "messaging_account_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "optimization_goal": {
                                "type": "string",
                                "enum": [
                                  "lead",
                                  "conversation",
                                  "qualified_seller"
                                ]
                              },
                              "ladder": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "waiting",
                                      "skipped",
                                      "stepping",
                                      "stepped",
                                      "failed"
                                    ],
                                    "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason_label": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Dealer-facing sentence (es-CL) for `reason`."
                                  },
                                  "qualified_7d": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "threshold": {
                                    "type": "number"
                                  },
                                  "campaign_age_days": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "min_campaign_age_days": {
                                    "type": "number"
                                  },
                                  "checked_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "status",
                                  "reason",
                                  "reason_label",
                                  "qualified_7d",
                                  "threshold",
                                  "campaign_age_days",
                                  "min_campaign_age_days",
                                  "checked_at"
                                ],
                                "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                              },
                              "creative_mix": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "mixed",
                                  "separate"
                                ],
                                "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                              },
                              "build_failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string",
                                    "description": "What happened, in plain words for the dealer."
                                  },
                                  "meta_message": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                  }
                                },
                                "required": [
                                  "code",
                                  "message"
                                ],
                                "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                              }
                            },
                            "required": [
                              "id",
                              "lane",
                              "daily_budget_clp",
                              "destination_url",
                              "messaging_account_id",
                              "optimization_goal",
                              "ladder",
                              "creative_mix",
                              "build_failure"
                            ]
                          }
                        },
                        "offer_snapshot": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "failure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "meta_objects": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "level": {
                                "type": "string",
                                "enum": [
                                  "campaign",
                                  "adset",
                                  "creative",
                                  "ad",
                                  "image",
                                  "video"
                                ]
                              },
                              "role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "parent_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "meta_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "media_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "angle": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "format": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "headline_mode": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "deleted_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "outside_edit_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Set when someone changed the object outside Vitrina; automatic steps skip it."
                              },
                              "outside_edit_fields": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "lane_id",
                              "level",
                              "role",
                              "parent_id",
                              "meta_id",
                              "media_kind",
                              "angle",
                              "format",
                              "headline_mode",
                              "created_on_meta_at",
                              "deleted_on_meta_at",
                              "outside_edit_at",
                              "outside_edit_fields"
                            ]
                          }
                        },
                        "activation": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "status",
                        "requires_review",
                        "reviewed_at",
                        "budget_package",
                        "daily_budget_clp",
                        "geo",
                        "status_changed_at",
                        "launched_at",
                        "created_at",
                        "vehicle_id",
                        "stock_paused_at",
                        "stock_pause_reason",
                        "lanes",
                        "offer_snapshot",
                        "failure",
                        "meta_objects",
                        "activation"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "plantilla": "captacion_consignaciones",
                    "status": "building",
                    "requires_review": true,
                    "reviewed_at": null,
                    "budget_package": "recomendado",
                    "daily_budget_clp": 60000,
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "location_types": [
                        "home"
                      ],
                      "meta_region_keys": [
                        "673"
                      ],
                      "label": "RM"
                    },
                    "status_changed_at": "2026-10-26T15:10:00.000Z",
                    "launched_at": null,
                    "vehicle_id": null,
                    "stock_paused_at": null,
                    "stock_pause_reason": null,
                    "created_at": "2026-10-26T15:10:00.000Z",
                    "lanes": [
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                        "lane": "landing",
                        "daily_budget_clp": 30000,
                        "destination_url": "https://www.tuautomotora.cl/vende-tu-auto",
                        "messaging_account_id": null,
                        "optimization_goal": "lead",
                        "ladder": null
                      },
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f602",
                        "lane": "whatsapp",
                        "daily_budget_clp": 30000,
                        "destination_url": null,
                        "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                        "optimization_goal": "conversation",
                        "ladder": null
                      }
                    ],
                    "offer_snapshot": null,
                    "failure": null,
                    "meta_objects": [],
                    "activation": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/lanzamientos/{id}": {
      "get": {
        "summary": "One Lanzamiento",
        "description": "One Lanzamiento with its lanes and every Meta object Vitrina created for it. An object changed outside Vitrina carries outside_edit_at and is skipped by every automatic step. `activation` follows a live Lanzamiento switching on (null | pending | done | failed).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Lanzamiento.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "captacion_consignaciones",
                            "venta_auto"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "building",
                            "paused_ready",
                            "in_review",
                            "live",
                            "failed",
                            "rolled_back"
                          ]
                        },
                        "requires_review": {
                          "type": "boolean",
                          "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                        },
                        "reviewed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "budget_package": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "daily_budget_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "status_changed_at": {
                          "type": "string"
                        },
                        "launched_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                        },
                        "stock_paused_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                        },
                        "stock_pause_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "sold",
                            "reserved",
                            "inactive",
                            "deleted"
                          ]
                        },
                        "lanes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane": {
                                "type": "string",
                                "enum": [
                                  "landing",
                                  "whatsapp"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "destination_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "messaging_account_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "optimization_goal": {
                                "type": "string",
                                "enum": [
                                  "lead",
                                  "conversation",
                                  "qualified_seller"
                                ]
                              },
                              "ladder": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "waiting",
                                      "skipped",
                                      "stepping",
                                      "stepped",
                                      "failed"
                                    ],
                                    "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason_label": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Dealer-facing sentence (es-CL) for `reason`."
                                  },
                                  "qualified_7d": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "threshold": {
                                    "type": "number"
                                  },
                                  "campaign_age_days": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "min_campaign_age_days": {
                                    "type": "number"
                                  },
                                  "checked_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "status",
                                  "reason",
                                  "reason_label",
                                  "qualified_7d",
                                  "threshold",
                                  "campaign_age_days",
                                  "min_campaign_age_days",
                                  "checked_at"
                                ],
                                "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                              },
                              "creative_mix": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "mixed",
                                  "separate"
                                ],
                                "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                              },
                              "build_failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string",
                                    "description": "What happened, in plain words for the dealer."
                                  },
                                  "meta_message": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                  }
                                },
                                "required": [
                                  "code",
                                  "message"
                                ],
                                "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                              }
                            },
                            "required": [
                              "id",
                              "lane",
                              "daily_budget_clp",
                              "destination_url",
                              "messaging_account_id",
                              "optimization_goal",
                              "ladder",
                              "creative_mix",
                              "build_failure"
                            ]
                          }
                        },
                        "offer_snapshot": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "failure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "meta_objects": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "level": {
                                "type": "string",
                                "enum": [
                                  "campaign",
                                  "adset",
                                  "creative",
                                  "ad",
                                  "image",
                                  "video"
                                ]
                              },
                              "role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "parent_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "meta_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "media_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "angle": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "format": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "headline_mode": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "deleted_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "outside_edit_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Set when someone changed the object outside Vitrina; automatic steps skip it."
                              },
                              "outside_edit_fields": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "lane_id",
                              "level",
                              "role",
                              "parent_id",
                              "meta_id",
                              "media_kind",
                              "angle",
                              "format",
                              "headline_mode",
                              "created_on_meta_at",
                              "deleted_on_meta_at",
                              "outside_edit_at",
                              "outside_edit_fields"
                            ]
                          }
                        },
                        "activation": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "status",
                        "requires_review",
                        "reviewed_at",
                        "budget_package",
                        "daily_budget_clp",
                        "geo",
                        "status_changed_at",
                        "launched_at",
                        "created_at",
                        "vehicle_id",
                        "stock_paused_at",
                        "stock_pause_reason",
                        "lanes",
                        "offer_snapshot",
                        "failure",
                        "meta_objects",
                        "activation"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "plantilla": "captacion_consignaciones",
                    "status": "paused_ready",
                    "requires_review": true,
                    "reviewed_at": null,
                    "budget_package": "recomendado",
                    "daily_budget_clp": 60000,
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "location_types": [
                        "home"
                      ]
                    },
                    "status_changed_at": "2026-10-26T15:10:00.000Z",
                    "launched_at": null,
                    "vehicle_id": null,
                    "stock_paused_at": null,
                    "stock_pause_reason": null,
                    "created_at": "2026-10-26T15:00:00.000Z",
                    "lanes": [
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                        "lane": "landing",
                        "daily_budget_clp": 30000,
                        "destination_url": "https://upautos.cl/vende-tu-auto",
                        "messaging_account_id": null,
                        "optimization_goal": "lead",
                        "ladder": null
                      },
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f602",
                        "lane": "whatsapp",
                        "daily_budget_clp": 30000,
                        "destination_url": null,
                        "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                        "optimization_goal": "conversation",
                        "ladder": null
                      }
                    ],
                    "offer_snapshot": {
                      "commission_pct": 5,
                      "commission_plus_iva": false,
                      "headlines": [
                        "commission",
                        "precio_minimo"
                      ],
                      "headline_mode": "commission",
                      "solo_pagas_si_se_vende": true,
                      "precio_minimo": true,
                      "compra_inmediata": true,
                      "test_drives_at_lot": true,
                      "transfer_help": true,
                      "photography": true,
                      "contract_length_days": 60,
                      "cars_sold_per_month": null
                    },
                    "failure": null,
                    "meta_objects": [],
                    "activation": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/consignaciones/metrics": {
      "get": {
        "summary": "Consignment metrics",
        "description": "The consignment funnel per Lanzamiento, per lane and per ad: spend → contacts (landing form + click-to-WhatsApp chat) → qualified sellers (the SellerQualified / AddToCart fact; a lead outside the Condiciones never counts) → inspection booked (SellerVisitScheduled) → Consignación captada (consignment or compra) → sold (the captured car sold from stock). Each step counts what happened in the window. Headline: cost_per_captada, with cost_per_qualified_seller beside it. Spend comes from the attribution engine; unknown spend is null (never 0) and so is every cost built on it, as is a cost over zero. Plus stock target progress: nothing pauses, Vitrina only suggests lowering the budget once reached.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only this Lanzamiento. Stock target progress always describes the whole workspace."
            },
            "required": false,
            "description": "Only this Lanzamiento. Stock target progress always describes the whole workspace.",
            "name": "lanzamiento_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Every «Captación de consignaciones» Lanzamiento (or the one asked for), totals and stock target progress.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string"
                            },
                            "to": {
                              "type": "string"
                            },
                            "timezone": {
                              "type": "string",
                              "enum": [
                                "America/Santiago"
                              ]
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "timezone"
                          ]
                        },
                        "spend_status": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "unavailable"
                          ],
                          "description": "Whether the attribution engine's spend read answered."
                        },
                        "totals": {
                          "type": "object",
                          "properties": {
                            "spend": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                            },
                            "contacts": {
                              "type": "integer",
                              "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                            },
                            "contacts_form": {
                              "type": "integer",
                              "description": "Landing form submissions."
                            },
                            "contacts_chat": {
                              "type": "integer",
                              "description": "Click-to-WhatsApp chats."
                            },
                            "qualified_sellers": {
                              "type": "integer",
                              "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                            },
                            "outside_criteria": {
                              "type": "integer",
                              "description": "Contacts in the window marked «fuera de criterio»."
                            },
                            "inspections_booked": {
                              "type": "integer",
                              "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                            },
                            "captadas": {
                              "type": "integer",
                              "description": "Consignaciones captadas in the window (consignment or compra)."
                            },
                            "captadas_consignacion": {
                              "type": "integer"
                            },
                            "captadas_compra": {
                              "type": "integer"
                            },
                            "sold": {
                              "type": "integer",
                              "description": "Captured cars sold from stock in the window (matched by patente)."
                            },
                            "cost_per_captada": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                            },
                            "cost_per_qualified_seller": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                            },
                            "cost_per_contact": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "spend",
                            "contacts",
                            "contacts_form",
                            "contacts_chat",
                            "qualified_sellers",
                            "outside_criteria",
                            "inspections_booked",
                            "captadas",
                            "captadas_consignacion",
                            "captadas_compra",
                            "sold",
                            "cost_per_captada",
                            "cost_per_qualified_seller",
                            "cost_per_contact"
                          ],
                          "description": "The consignment funnel of one grain, in the window."
                        },
                        "lanzamientos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "plantilla": {
                                "type": "string",
                                "enum": [
                                  "captacion_consignaciones",
                                  "venta_auto"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "draft",
                                  "building",
                                  "paused_ready",
                                  "in_review",
                                  "live",
                                  "failed",
                                  "rolled_back"
                                ]
                              },
                              "launched_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "spend_status": {
                                "type": "string",
                                "enum": [
                                  "ok",
                                  "unavailable",
                                  "not_reported"
                                ],
                                "description": "ok = the attribution engine reports this Lanzamiento; not_reported = the read worked but none of its ads or campaigns appear (spend null); unavailable = the read failed (spend null)."
                              },
                              "funnel": {
                                "type": "object",
                                "properties": {
                                  "spend": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                  },
                                  "contacts": {
                                    "type": "integer",
                                    "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                  },
                                  "contacts_form": {
                                    "type": "integer",
                                    "description": "Landing form submissions."
                                  },
                                  "contacts_chat": {
                                    "type": "integer",
                                    "description": "Click-to-WhatsApp chats."
                                  },
                                  "qualified_sellers": {
                                    "type": "integer",
                                    "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                  },
                                  "outside_criteria": {
                                    "type": "integer",
                                    "description": "Contacts in the window marked «fuera de criterio»."
                                  },
                                  "inspections_booked": {
                                    "type": "integer",
                                    "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                  },
                                  "captadas": {
                                    "type": "integer",
                                    "description": "Consignaciones captadas in the window (consignment or compra)."
                                  },
                                  "captadas_consignacion": {
                                    "type": "integer"
                                  },
                                  "captadas_compra": {
                                    "type": "integer"
                                  },
                                  "sold": {
                                    "type": "integer",
                                    "description": "Captured cars sold from stock in the window (matched by patente)."
                                  },
                                  "cost_per_captada": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                  },
                                  "cost_per_qualified_seller": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                  },
                                  "cost_per_contact": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "spend",
                                  "contacts",
                                  "contacts_form",
                                  "contacts_chat",
                                  "qualified_sellers",
                                  "outside_criteria",
                                  "inspections_booked",
                                  "captadas",
                                  "captadas_consignacion",
                                  "captadas_compra",
                                  "sold",
                                  "cost_per_captada",
                                  "cost_per_qualified_seller",
                                  "cost_per_contact"
                                ],
                                "description": "The consignment funnel of one grain, in the window."
                              },
                              "lanes": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "lane_id": {
                                      "type": "string"
                                    },
                                    "lane": {
                                      "type": "string",
                                      "enum": [
                                        "landing",
                                        "whatsapp"
                                      ]
                                    },
                                    "funnel": {
                                      "type": "object",
                                      "properties": {
                                        "spend": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                        },
                                        "contacts": {
                                          "type": "integer",
                                          "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                        },
                                        "contacts_form": {
                                          "type": "integer",
                                          "description": "Landing form submissions."
                                        },
                                        "contacts_chat": {
                                          "type": "integer",
                                          "description": "Click-to-WhatsApp chats."
                                        },
                                        "qualified_sellers": {
                                          "type": "integer",
                                          "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                        },
                                        "outside_criteria": {
                                          "type": "integer",
                                          "description": "Contacts in the window marked «fuera de criterio»."
                                        },
                                        "inspections_booked": {
                                          "type": "integer",
                                          "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                        },
                                        "captadas": {
                                          "type": "integer",
                                          "description": "Consignaciones captadas in the window (consignment or compra)."
                                        },
                                        "captadas_consignacion": {
                                          "type": "integer"
                                        },
                                        "captadas_compra": {
                                          "type": "integer"
                                        },
                                        "sold": {
                                          "type": "integer",
                                          "description": "Captured cars sold from stock in the window (matched by patente)."
                                        },
                                        "cost_per_captada": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                        },
                                        "cost_per_qualified_seller": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                        },
                                        "cost_per_contact": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "spend",
                                        "contacts",
                                        "contacts_form",
                                        "contacts_chat",
                                        "qualified_sellers",
                                        "outside_criteria",
                                        "inspections_booked",
                                        "captadas",
                                        "captadas_consignacion",
                                        "captadas_compra",
                                        "sold",
                                        "cost_per_captada",
                                        "cost_per_qualified_seller",
                                        "cost_per_contact"
                                      ],
                                      "description": "The consignment funnel of one grain, in the window."
                                    }
                                  },
                                  "required": [
                                    "lane_id",
                                    "lane",
                                    "funnel"
                                  ]
                                }
                              },
                              "ads": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "ad_object_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "Vitrina's ad record. null for an ad found under a Lanzamiento campaign that Vitrina did not create, or for the «no ad identified» row."
                                    },
                                    "meta_ad_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "null only on the «no ad identified» row (leads linked by lane only)."
                                    },
                                    "lane_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "lane": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "enum": [
                                        "landing",
                                        "whatsapp"
                                      ]
                                    },
                                    "angle": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "format": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "headline_mode": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "media_kind": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "outside_vitrina": {
                                      "type": "boolean",
                                      "description": "An ad under a Lanzamiento campaign that Vitrina did not create."
                                    },
                                    "funnel": {
                                      "type": "object",
                                      "properties": {
                                        "spend": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                        },
                                        "contacts": {
                                          "type": "integer",
                                          "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                        },
                                        "contacts_form": {
                                          "type": "integer",
                                          "description": "Landing form submissions."
                                        },
                                        "contacts_chat": {
                                          "type": "integer",
                                          "description": "Click-to-WhatsApp chats."
                                        },
                                        "qualified_sellers": {
                                          "type": "integer",
                                          "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                        },
                                        "outside_criteria": {
                                          "type": "integer",
                                          "description": "Contacts in the window marked «fuera de criterio»."
                                        },
                                        "inspections_booked": {
                                          "type": "integer",
                                          "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                        },
                                        "captadas": {
                                          "type": "integer",
                                          "description": "Consignaciones captadas in the window (consignment or compra)."
                                        },
                                        "captadas_consignacion": {
                                          "type": "integer"
                                        },
                                        "captadas_compra": {
                                          "type": "integer"
                                        },
                                        "sold": {
                                          "type": "integer",
                                          "description": "Captured cars sold from stock in the window (matched by patente)."
                                        },
                                        "cost_per_captada": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                        },
                                        "cost_per_qualified_seller": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                        },
                                        "cost_per_contact": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "spend",
                                        "contacts",
                                        "contacts_form",
                                        "contacts_chat",
                                        "qualified_sellers",
                                        "outside_criteria",
                                        "inspections_booked",
                                        "captadas",
                                        "captadas_consignacion",
                                        "captadas_compra",
                                        "sold",
                                        "cost_per_captada",
                                        "cost_per_qualified_seller",
                                        "cost_per_contact"
                                      ],
                                      "description": "The consignment funnel of one grain, in the window."
                                    }
                                  },
                                  "required": [
                                    "ad_object_id",
                                    "meta_ad_id",
                                    "lane_id",
                                    "lane",
                                    "angle",
                                    "format",
                                    "headline_mode",
                                    "media_kind",
                                    "outside_vitrina",
                                    "funnel"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "id",
                              "plantilla",
                              "status",
                              "launched_at",
                              "spend_status",
                              "funnel",
                              "lanes",
                              "ads"
                            ]
                          }
                        },
                        "stock_target": {
                          "type": "object",
                          "properties": {
                            "in_stock": {
                              "type": "integer",
                              "description": "Cars in stock now (active, not sold)."
                            },
                            "captured_this_month": {
                              "type": "integer",
                              "description": "Consignaciones captadas from any Lanzamiento this calendar month."
                            },
                            "target": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "reached": {
                              "type": [
                                "boolean",
                                "null"
                              ],
                              "description": "in_stock >= target; null without a target."
                            },
                            "progress": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "in_stock / target; null without a target."
                            },
                            "suggest_lower_budget": {
                              "type": "boolean",
                              "description": "Target reached while a Lanzamiento is live: suggest «considera bajar el presupuesto». Nothing pauses."
                            }
                          },
                          "required": [
                            "in_stock",
                            "captured_this_month",
                            "target",
                            "reached",
                            "progress",
                            "suggest_lower_budget"
                          ]
                        }
                      },
                      "required": [
                        "period",
                        "spend_status",
                        "totals",
                        "lanzamientos",
                        "stock_target"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": {
                      "from": "2026-10-01",
                      "to": "2026-10-26",
                      "timezone": "America/Santiago"
                    },
                    "spend_status": "ok",
                    "totals": {
                      "spend": 412000,
                      "contacts": 58,
                      "contacts_form": 21,
                      "contacts_chat": 37,
                      "qualified_sellers": 19,
                      "outside_criteria": 9,
                      "inspections_booked": 11,
                      "captadas": 4,
                      "captadas_consignacion": 3,
                      "captadas_compra": 1,
                      "sold": 1,
                      "cost_per_captada": 103000,
                      "cost_per_qualified_seller": 21684,
                      "cost_per_contact": 7103
                    },
                    "lanzamientos": [
                      {
                        "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                        "plantilla": "captacion_consignaciones",
                        "status": "live",
                        "launched_at": "2026-10-02T13:00:00.000Z",
                        "spend_status": "ok",
                        "funnel": {
                          "spend": 412000,
                          "contacts": 58,
                          "contacts_form": 21,
                          "contacts_chat": 37,
                          "qualified_sellers": 19,
                          "outside_criteria": 9,
                          "inspections_booked": 11,
                          "captadas": 4,
                          "captadas_consignacion": 3,
                          "captadas_compra": 1,
                          "sold": 1,
                          "cost_per_captada": 103000,
                          "cost_per_qualified_seller": 21684,
                          "cost_per_contact": 7103
                        },
                        "lanes": [
                          {
                            "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                            "lane": "landing",
                            "funnel": {
                              "spend": 206000,
                              "contacts": 21,
                              "contacts_form": 21,
                              "contacts_chat": 0,
                              "qualified_sellers": 8,
                              "outside_criteria": 3,
                              "inspections_booked": 5,
                              "captadas": 2,
                              "captadas_consignacion": 2,
                              "captadas_compra": 0,
                              "sold": 0,
                              "cost_per_captada": 103000,
                              "cost_per_qualified_seller": 25750,
                              "cost_per_contact": 9810
                            }
                          }
                        ],
                        "ads": [
                          {
                            "ad_object_id": "9d0e1f2a-3b4c-4d5e-8f60-718293a4b5c6",
                            "meta_ad_id": "120211234567890123",
                            "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                            "lane": "landing",
                            "angle": "comision",
                            "format": "4:5",
                            "headline_mode": "commission",
                            "media_kind": "static",
                            "outside_vitrina": false,
                            "funnel": {
                              "spend": 0,
                              "contacts": 0,
                              "contacts_form": 0,
                              "contacts_chat": 0,
                              "qualified_sellers": 0,
                              "outside_criteria": 0,
                              "inspections_booked": 0,
                              "captadas": 0,
                              "captadas_consignacion": 0,
                              "captadas_compra": 0,
                              "sold": 0,
                              "cost_per_captada": null,
                              "cost_per_qualified_seller": null,
                              "cost_per_contact": null
                            }
                          }
                        ]
                      }
                    ],
                    "stock_target": {
                      "in_stock": 31,
                      "captured_this_month": 4,
                      "target": 35,
                      "reached": false,
                      "progress": 0.886,
                      "suggest_lower_budget": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/{id}/metrics": {
      "get": {
        "summary": "Consignment metrics of one Lanzamiento",
        "description": "The consignment funnel per Lanzamiento, per lane and per ad: spend → contacts (landing form + click-to-WhatsApp chat) → qualified sellers (the SellerQualified / AddToCart fact; a lead outside the Condiciones never counts) → inspection booked (SellerVisitScheduled) → Consignación captada (consignment or compra) → sold (the captured car sold from stock). Each step counts what happened in the window. Headline: cost_per_captada, with cost_per_qualified_seller beside it. Spend comes from the attribution engine; unknown spend is null (never 0) and so is every cost built on it, as is a cost over zero. Plus stock target progress: nothing pauses, Vitrina only suggests lowering the budget once reached.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The Lanzamiento in `lanzamientos[0]`, plus stock target progress for the workspace.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string"
                            },
                            "to": {
                              "type": "string"
                            },
                            "timezone": {
                              "type": "string",
                              "enum": [
                                "America/Santiago"
                              ]
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "timezone"
                          ]
                        },
                        "spend_status": {
                          "type": "string",
                          "enum": [
                            "ok",
                            "unavailable"
                          ],
                          "description": "Whether the attribution engine's spend read answered."
                        },
                        "totals": {
                          "type": "object",
                          "properties": {
                            "spend": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                            },
                            "contacts": {
                              "type": "integer",
                              "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                            },
                            "contacts_form": {
                              "type": "integer",
                              "description": "Landing form submissions."
                            },
                            "contacts_chat": {
                              "type": "integer",
                              "description": "Click-to-WhatsApp chats."
                            },
                            "qualified_sellers": {
                              "type": "integer",
                              "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                            },
                            "outside_criteria": {
                              "type": "integer",
                              "description": "Contacts in the window marked «fuera de criterio»."
                            },
                            "inspections_booked": {
                              "type": "integer",
                              "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                            },
                            "captadas": {
                              "type": "integer",
                              "description": "Consignaciones captadas in the window (consignment or compra)."
                            },
                            "captadas_consignacion": {
                              "type": "integer"
                            },
                            "captadas_compra": {
                              "type": "integer"
                            },
                            "sold": {
                              "type": "integer",
                              "description": "Captured cars sold from stock in the window (matched by patente)."
                            },
                            "cost_per_captada": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                            },
                            "cost_per_qualified_seller": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                            },
                            "cost_per_contact": {
                              "type": [
                                "number",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "spend",
                            "contacts",
                            "contacts_form",
                            "contacts_chat",
                            "qualified_sellers",
                            "outside_criteria",
                            "inspections_booked",
                            "captadas",
                            "captadas_consignacion",
                            "captadas_compra",
                            "sold",
                            "cost_per_captada",
                            "cost_per_qualified_seller",
                            "cost_per_contact"
                          ],
                          "description": "The consignment funnel of one grain, in the window."
                        },
                        "lanzamientos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "plantilla": {
                                "type": "string",
                                "enum": [
                                  "captacion_consignaciones",
                                  "venta_auto"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "draft",
                                  "building",
                                  "paused_ready",
                                  "in_review",
                                  "live",
                                  "failed",
                                  "rolled_back"
                                ]
                              },
                              "launched_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "spend_status": {
                                "type": "string",
                                "enum": [
                                  "ok",
                                  "unavailable",
                                  "not_reported"
                                ],
                                "description": "ok = the attribution engine reports this Lanzamiento; not_reported = the read worked but none of its ads or campaigns appear (spend null); unavailable = the read failed (spend null)."
                              },
                              "funnel": {
                                "type": "object",
                                "properties": {
                                  "spend": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                  },
                                  "contacts": {
                                    "type": "integer",
                                    "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                  },
                                  "contacts_form": {
                                    "type": "integer",
                                    "description": "Landing form submissions."
                                  },
                                  "contacts_chat": {
                                    "type": "integer",
                                    "description": "Click-to-WhatsApp chats."
                                  },
                                  "qualified_sellers": {
                                    "type": "integer",
                                    "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                  },
                                  "outside_criteria": {
                                    "type": "integer",
                                    "description": "Contacts in the window marked «fuera de criterio»."
                                  },
                                  "inspections_booked": {
                                    "type": "integer",
                                    "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                  },
                                  "captadas": {
                                    "type": "integer",
                                    "description": "Consignaciones captadas in the window (consignment or compra)."
                                  },
                                  "captadas_consignacion": {
                                    "type": "integer"
                                  },
                                  "captadas_compra": {
                                    "type": "integer"
                                  },
                                  "sold": {
                                    "type": "integer",
                                    "description": "Captured cars sold from stock in the window (matched by patente)."
                                  },
                                  "cost_per_captada": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                  },
                                  "cost_per_qualified_seller": {
                                    "type": [
                                      "number",
                                      "null"
                                    ],
                                    "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                  },
                                  "cost_per_contact": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "spend",
                                  "contacts",
                                  "contacts_form",
                                  "contacts_chat",
                                  "qualified_sellers",
                                  "outside_criteria",
                                  "inspections_booked",
                                  "captadas",
                                  "captadas_consignacion",
                                  "captadas_compra",
                                  "sold",
                                  "cost_per_captada",
                                  "cost_per_qualified_seller",
                                  "cost_per_contact"
                                ],
                                "description": "The consignment funnel of one grain, in the window."
                              },
                              "lanes": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "lane_id": {
                                      "type": "string"
                                    },
                                    "lane": {
                                      "type": "string",
                                      "enum": [
                                        "landing",
                                        "whatsapp"
                                      ]
                                    },
                                    "funnel": {
                                      "type": "object",
                                      "properties": {
                                        "spend": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                        },
                                        "contacts": {
                                          "type": "integer",
                                          "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                        },
                                        "contacts_form": {
                                          "type": "integer",
                                          "description": "Landing form submissions."
                                        },
                                        "contacts_chat": {
                                          "type": "integer",
                                          "description": "Click-to-WhatsApp chats."
                                        },
                                        "qualified_sellers": {
                                          "type": "integer",
                                          "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                        },
                                        "outside_criteria": {
                                          "type": "integer",
                                          "description": "Contacts in the window marked «fuera de criterio»."
                                        },
                                        "inspections_booked": {
                                          "type": "integer",
                                          "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                        },
                                        "captadas": {
                                          "type": "integer",
                                          "description": "Consignaciones captadas in the window (consignment or compra)."
                                        },
                                        "captadas_consignacion": {
                                          "type": "integer"
                                        },
                                        "captadas_compra": {
                                          "type": "integer"
                                        },
                                        "sold": {
                                          "type": "integer",
                                          "description": "Captured cars sold from stock in the window (matched by patente)."
                                        },
                                        "cost_per_captada": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                        },
                                        "cost_per_qualified_seller": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                        },
                                        "cost_per_contact": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "spend",
                                        "contacts",
                                        "contacts_form",
                                        "contacts_chat",
                                        "qualified_sellers",
                                        "outside_criteria",
                                        "inspections_booked",
                                        "captadas",
                                        "captadas_consignacion",
                                        "captadas_compra",
                                        "sold",
                                        "cost_per_captada",
                                        "cost_per_qualified_seller",
                                        "cost_per_contact"
                                      ],
                                      "description": "The consignment funnel of one grain, in the window."
                                    }
                                  },
                                  "required": [
                                    "lane_id",
                                    "lane",
                                    "funnel"
                                  ]
                                }
                              },
                              "ads": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "ad_object_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "Vitrina's ad record. null for an ad found under a Lanzamiento campaign that Vitrina did not create, or for the «no ad identified» row."
                                    },
                                    "meta_ad_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "null only on the «no ad identified» row (leads linked by lane only)."
                                    },
                                    "lane_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "lane": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "enum": [
                                        "landing",
                                        "whatsapp"
                                      ]
                                    },
                                    "angle": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "format": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "headline_mode": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "media_kind": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "outside_vitrina": {
                                      "type": "boolean",
                                      "description": "An ad under a Lanzamiento campaign that Vitrina did not create."
                                    },
                                    "funnel": {
                                      "type": "object",
                                      "properties": {
                                        "spend": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "CLP spent in the window (the attribution engine). null = unknown, never 0."
                                        },
                                        "contacts": {
                                          "type": "integer",
                                          "description": "Leads linked to the Lanzamiento in the window (form + chat)."
                                        },
                                        "contacts_form": {
                                          "type": "integer",
                                          "description": "Landing form submissions."
                                        },
                                        "contacts_chat": {
                                          "type": "integer",
                                          "description": "Click-to-WhatsApp chats."
                                        },
                                        "qualified_sellers": {
                                          "type": "integer",
                                          "description": "Sellers qualified in the window (the SellerQualified / AddToCart fact). A lead outside the Condiciones never counts."
                                        },
                                        "outside_criteria": {
                                          "type": "integer",
                                          "description": "Contacts in the window marked «fuera de criterio»."
                                        },
                                        "inspections_booked": {
                                          "type": "integer",
                                          "description": "Inspections booked in the window (the SellerVisitScheduled fact)."
                                        },
                                        "captadas": {
                                          "type": "integer",
                                          "description": "Consignaciones captadas in the window (consignment or compra)."
                                        },
                                        "captadas_consignacion": {
                                          "type": "integer"
                                        },
                                        "captadas_compra": {
                                          "type": "integer"
                                        },
                                        "sold": {
                                          "type": "integer",
                                          "description": "Captured cars sold from stock in the window (matched by patente)."
                                        },
                                        "cost_per_captada": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "Headline: spend / captadas, whole CLP. null when spend is unknown or there is no captada."
                                        },
                                        "cost_per_qualified_seller": {
                                          "type": [
                                            "number",
                                            "null"
                                          ],
                                          "description": "spend / qualified sellers, whole CLP; null on unknown spend or zero."
                                        },
                                        "cost_per_contact": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "spend",
                                        "contacts",
                                        "contacts_form",
                                        "contacts_chat",
                                        "qualified_sellers",
                                        "outside_criteria",
                                        "inspections_booked",
                                        "captadas",
                                        "captadas_consignacion",
                                        "captadas_compra",
                                        "sold",
                                        "cost_per_captada",
                                        "cost_per_qualified_seller",
                                        "cost_per_contact"
                                      ],
                                      "description": "The consignment funnel of one grain, in the window."
                                    }
                                  },
                                  "required": [
                                    "ad_object_id",
                                    "meta_ad_id",
                                    "lane_id",
                                    "lane",
                                    "angle",
                                    "format",
                                    "headline_mode",
                                    "media_kind",
                                    "outside_vitrina",
                                    "funnel"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "id",
                              "plantilla",
                              "status",
                              "launched_at",
                              "spend_status",
                              "funnel",
                              "lanes",
                              "ads"
                            ]
                          }
                        },
                        "stock_target": {
                          "type": "object",
                          "properties": {
                            "in_stock": {
                              "type": "integer",
                              "description": "Cars in stock now (active, not sold)."
                            },
                            "captured_this_month": {
                              "type": "integer",
                              "description": "Consignaciones captadas from any Lanzamiento this calendar month."
                            },
                            "target": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "reached": {
                              "type": [
                                "boolean",
                                "null"
                              ],
                              "description": "in_stock >= target; null without a target."
                            },
                            "progress": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "in_stock / target; null without a target."
                            },
                            "suggest_lower_budget": {
                              "type": "boolean",
                              "description": "Target reached while a Lanzamiento is live: suggest «considera bajar el presupuesto». Nothing pauses."
                            }
                          },
                          "required": [
                            "in_stock",
                            "captured_this_month",
                            "target",
                            "reached",
                            "progress",
                            "suggest_lower_budget"
                          ]
                        }
                      },
                      "required": [
                        "period",
                        "spend_status",
                        "totals",
                        "lanzamientos",
                        "stock_target"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": {
                      "from": "2026-10-01",
                      "to": "2026-10-26",
                      "timezone": "America/Santiago"
                    },
                    "spend_status": "ok",
                    "totals": {
                      "spend": 412000,
                      "contacts": 58,
                      "contacts_form": 21,
                      "contacts_chat": 37,
                      "qualified_sellers": 19,
                      "outside_criteria": 9,
                      "inspections_booked": 11,
                      "captadas": 4,
                      "captadas_consignacion": 3,
                      "captadas_compra": 1,
                      "sold": 1,
                      "cost_per_captada": 103000,
                      "cost_per_qualified_seller": 21684,
                      "cost_per_contact": 7103
                    },
                    "lanzamientos": [
                      {
                        "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                        "plantilla": "captacion_consignaciones",
                        "status": "live",
                        "launched_at": "2026-10-02T13:00:00.000Z",
                        "spend_status": "ok",
                        "funnel": {
                          "spend": 412000,
                          "contacts": 58,
                          "contacts_form": 21,
                          "contacts_chat": 37,
                          "qualified_sellers": 19,
                          "outside_criteria": 9,
                          "inspections_booked": 11,
                          "captadas": 4,
                          "captadas_consignacion": 3,
                          "captadas_compra": 1,
                          "sold": 1,
                          "cost_per_captada": 103000,
                          "cost_per_qualified_seller": 21684,
                          "cost_per_contact": 7103
                        },
                        "lanes": [
                          {
                            "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                            "lane": "landing",
                            "funnel": {
                              "spend": 206000,
                              "contacts": 21,
                              "contacts_form": 21,
                              "contacts_chat": 0,
                              "qualified_sellers": 8,
                              "outside_criteria": 3,
                              "inspections_booked": 5,
                              "captadas": 2,
                              "captadas_consignacion": 2,
                              "captadas_compra": 0,
                              "sold": 0,
                              "cost_per_captada": 103000,
                              "cost_per_qualified_seller": 25750,
                              "cost_per_contact": 9810
                            }
                          }
                        ],
                        "ads": [
                          {
                            "ad_object_id": "9d0e1f2a-3b4c-4d5e-8f60-718293a4b5c6",
                            "meta_ad_id": "120211234567890123",
                            "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                            "lane": "landing",
                            "angle": "comision",
                            "format": "4:5",
                            "headline_mode": "commission",
                            "media_kind": "static",
                            "outside_vitrina": false,
                            "funnel": {
                              "spend": 0,
                              "contacts": 0,
                              "contacts_form": 0,
                              "contacts_chat": 0,
                              "qualified_sellers": 0,
                              "outside_criteria": 0,
                              "inspections_booked": 0,
                              "captadas": 0,
                              "captadas_consignacion": 0,
                              "captadas_compra": 0,
                              "sold": 0,
                              "cost_per_captada": null,
                              "cost_per_qualified_seller": null,
                              "cost_per_contact": null
                            }
                          }
                        ]
                      }
                    ],
                    "stock_target": {
                      "in_stock": 31,
                      "captured_this_month": 4,
                      "target": 35,
                      "reached": false,
                      "progress": 0.886,
                      "suggest_lower_budget": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/preflight": {
      "post": {
        "summary": "Check a Lanzamiento before creating it",
        "description": "Everything the wizard shows before the dealer presses «Crear»: the budget split per lane and per ad set, the ad account's daily spending limit and a warning when the total exceeds it (limit_status unknown means Meta could not be read: never treat it as 0), the Facebook Pages and the dataset the ads will use, the WhatsApp number, the default geo (the dealer's region: people who live in it or are regularly there) and every problem that would refuse the create. Reads only; creates nothing.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "budget": {
                    "type": "object",
                    "properties": {
                      "package": {
                        "type": "string",
                        "enum": [
                          "inicial",
                          "recomendado",
                          "intensivo",
                          "custom"
                        ]
                      },
                      "custom_daily_total_clp": {
                        "type": [
                          "integer",
                          "null"
                        ],
                        "minimum": 40000,
                        "maximum": 10000000,
                        "description": "Required for `custom`: the daily total across both lanes, CLP."
                      }
                    },
                    "required": [
                      "package"
                    ],
                    "description": "Daily total across lanes: Inicial $40k, Recomendado $60k, Intensivo $100k, or custom ≥ $40k. Split evenly across the ad sets of every lane, ≥ $20k per ad set."
                  },
                  "lanes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "lane": {
                          "type": "string",
                          "enum": [
                            "landing",
                            "whatsapp"
                          ]
                        },
                        "messaging_account_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "whatsapp: the connected WhatsApp the ads open."
                        }
                      },
                      "required": [
                        "lane"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 2
                  },
                  "with_video": {
                    "type": "boolean",
                    "default": false,
                    "description": "A video was picked (with `creative_mix: separate` it adds a video ad set per lane, which changes the per-ad-set split)."
                  },
                  "creative_mix": {
                    "type": "string",
                    "enum": [
                      "mixed",
                      "separate"
                    ],
                    "description": "How videos sit next to statics on every lane. `mixed`: ONE ad set per lane with the videos as extra ads beside the statics (they compete; Meta delivers more of whichever works). `separate`: a video ad set per lane next to the statics one (two ad sets per lane, ≥ $20k each). Omitted: `separate` when the budget funds ≥ $20k per separate ad set, else `mixed`. The preflight's `budget.creative_mix` says which one applies."
                  },
                  "region_codes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(0?[1-9]|1[0-6])$"
                    },
                    "maxItems": 16,
                    "description": "CUT region codes; omitted = the dealer's own region."
                  },
                  "page_id": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "dataset_id": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "budget",
                  "lanes"
                ]
              },
              "example": {
                "budget": {
                  "package": "inicial"
                },
                "lanes": [
                  {
                    "lane": "landing"
                  },
                  {
                    "lane": "whatsapp",
                    "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5"
                  }
                ],
                "with_video": true,
                "creative_mix": "mixed"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "budget": {
                          "type": "object",
                          "properties": {
                            "ok": {
                              "type": "boolean"
                            },
                            "package": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "daily_total_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "per_lane_clp": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              }
                            },
                            "per_adset_clp": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              }
                            },
                            "message": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "creative_mix": {
                              "type": "string",
                              "enum": [
                                "mixed",
                                "separate"
                              ],
                              "description": "The mix this request builds with (its own, or the default)."
                            },
                            "adsets_per_lane": {
                              "type": "object",
                              "additionalProperties": {
                                "type": "number"
                              },
                              "description": "Ad sets each lane runs with that mix."
                            },
                            "min_daily_total_clp": {
                              "type": "number",
                              "description": "The smallest daily total these ad sets accept: $20k per ad set, at least $40k."
                            },
                            "packages": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "package": {
                                    "type": "string",
                                    "enum": [
                                      "inicial",
                                      "recomendado",
                                      "intensivo"
                                    ]
                                  },
                                  "daily_total_clp": {
                                    "type": "number"
                                  },
                                  "ok": {
                                    "type": "boolean"
                                  },
                                  "per_adset_clp": {
                                    "type": "object",
                                    "additionalProperties": {
                                      "type": "number"
                                    }
                                  }
                                },
                                "required": [
                                  "package",
                                  "daily_total_clp",
                                  "ok",
                                  "per_adset_clp"
                                ]
                              },
                              "description": "Every fixed package, checked against the ad sets planned."
                            }
                          },
                          "required": [
                            "ok",
                            "package",
                            "daily_total_clp",
                            "per_lane_clp",
                            "per_adset_clp",
                            "message",
                            "creative_mix",
                            "adsets_per_lane",
                            "min_daily_total_clp",
                            "packages"
                          ]
                        },
                        "ad_account": {
                          "type": "object",
                          "properties": {
                            "readable": {
                              "type": "boolean"
                            },
                            "ad_account_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "currency": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "daily_spending_limit_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "null = unknown or no limit; see limit_status. Never 0 for unknown."
                            },
                            "limit_status": {
                              "type": "string",
                              "enum": [
                                "known",
                                "none",
                                "unknown"
                              ],
                              "description": "unknown = Meta could not be read: render «unknown», never 0."
                            },
                            "spend_today_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "exceeds_limit": {
                              "type": [
                                "boolean",
                                "null"
                              ],
                              "description": "null when the limit is unknown."
                            },
                            "warning": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "readable",
                            "ad_account_id",
                            "currency",
                            "daily_spending_limit_clp",
                            "limit_status",
                            "spend_today_clp",
                            "exceeds_limit",
                            "warning"
                          ]
                        },
                        "pages": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "page_id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "page_id",
                              "name"
                            ]
                          }
                        },
                        "page_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dataset": {
                          "type": "object",
                          "properties": {
                            "dataset_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "selectable": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "dataset_id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "dataset_id",
                                  "name"
                                ]
                              }
                            }
                          },
                          "required": [
                            "dataset_id",
                            "selectable"
                          ]
                        },
                        "whatsapp": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "messaging_account_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "phone_known": {
                              "type": "boolean"
                            },
                            "page_link": {
                              "type": "string",
                              "enum": [
                                "linked",
                                "not_linked",
                                "unknown"
                              ],
                              "description": "Whether the number is linked to the ad’s Facebook Page. Meta offers no read for it, so `unknown` unless an earlier Lanzamiento got Meta’s answer for this Page and number (`not_linked`: Meta refused the click-to-WhatsApp ad set; `linked`: it built). Never a blocker."
                            },
                            "page_link_warning": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Plain words for the dealer when the link is not known to be there; null when `page_link` is `linked`."
                            }
                          },
                          "required": [
                            "messaging_account_id",
                            "phone_known",
                            "page_link",
                            "page_link_warning"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "properties": {
                            "region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "label": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "location_types": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "enum": [
                                  "home"
                                ]
                              }
                            },
                            "supported": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "region_codes",
                            "label",
                            "location_types",
                            "supported"
                          ]
                        },
                        "headlines": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              },
                              "lane": {
                                "type": "string",
                                "enum": [
                                  "landing",
                                  "whatsapp"
                                ]
                              }
                            },
                            "required": [
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "budget",
                        "ad_account",
                        "pages",
                        "page_id",
                        "dataset",
                        "whatsapp",
                        "geo",
                        "headlines",
                        "problems"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "budget": {
                      "ok": true,
                      "package": "inicial",
                      "daily_total_clp": 40000,
                      "per_lane_clp": {
                        "landing": 20000,
                        "whatsapp": 20000
                      },
                      "per_adset_clp": {
                        "landing": 20000,
                        "whatsapp": 20000
                      },
                      "message": null,
                      "creative_mix": "mixed",
                      "adsets_per_lane": {
                        "landing": 1,
                        "whatsapp": 1
                      },
                      "min_daily_total_clp": 40000,
                      "packages": [
                        {
                          "package": "inicial",
                          "daily_total_clp": 40000,
                          "ok": true,
                          "per_adset_clp": {
                            "landing": 20000,
                            "whatsapp": 20000
                          }
                        },
                        {
                          "package": "recomendado",
                          "daily_total_clp": 60000,
                          "ok": true,
                          "per_adset_clp": {
                            "landing": 30000,
                            "whatsapp": 30000
                          }
                        },
                        {
                          "package": "intensivo",
                          "daily_total_clp": 100000,
                          "ok": true,
                          "per_adset_clp": {
                            "landing": 50000,
                            "whatsapp": 50000
                          }
                        }
                      ]
                    },
                    "ad_account": {
                      "readable": true,
                      "ad_account_id": "act_100000000000001",
                      "currency": "CLP",
                      "daily_spending_limit_clp": null,
                      "limit_status": "unknown",
                      "spend_today_clp": 0,
                      "exceeds_limit": null,
                      "warning": "No pudimos leer el límite de gasto diario de tu cuenta en Meta."
                    },
                    "pages": [
                      {
                        "page_id": "100000000000002",
                        "name": "Tu Automotora"
                      }
                    ],
                    "page_id": "100000000000002",
                    "dataset": {
                      "dataset_id": "100000000000003",
                      "selectable": []
                    },
                    "whatsapp": {
                      "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                      "phone_known": true,
                      "page_link": "unknown",
                      "page_link_warning": "Antes de lanzar por WhatsApp, el número tiene que estar vinculado a tu página «Tu Automotora» (en la página: Configuración, Cuentas vinculadas, WhatsApp). Desde aquí no podemos comprobarlo; si no lo está, Meta rechaza el canal de WhatsApp y solo se crea el Sitio."
                    },
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "label": "RM",
                      "location_types": [
                        "home"
                      ],
                      "supported": true
                    },
                    "headlines": [
                      "commission",
                      "solo_si_vende"
                    ],
                    "problems": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/lanzamientos/{id}/confirm": {
      "post": {
        "summary": "Confirm a built Lanzamiento",
        "description": "The dealer confirms a paused_ready Lanzamiento. The dealer's first Lanzamiento goes to in_review and waits for Vitrina; otherwise it goes live and its ads, ad sets and campaigns are switched on in the background (activation). The CBO Winners campaign stays paused. 409 from any other status.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The Lanzamiento after the confirm.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "captacion_consignaciones",
                            "venta_auto"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "building",
                            "paused_ready",
                            "in_review",
                            "live",
                            "failed",
                            "rolled_back"
                          ]
                        },
                        "requires_review": {
                          "type": "boolean",
                          "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                        },
                        "reviewed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "budget_package": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "daily_budget_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "status_changed_at": {
                          "type": "string"
                        },
                        "launched_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                        },
                        "stock_paused_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                        },
                        "stock_pause_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "sold",
                            "reserved",
                            "inactive",
                            "deleted"
                          ]
                        },
                        "lanes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane": {
                                "type": "string",
                                "enum": [
                                  "landing",
                                  "whatsapp"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "destination_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "messaging_account_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "optimization_goal": {
                                "type": "string",
                                "enum": [
                                  "lead",
                                  "conversation",
                                  "qualified_seller"
                                ]
                              },
                              "ladder": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "waiting",
                                      "skipped",
                                      "stepping",
                                      "stepped",
                                      "failed"
                                    ],
                                    "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason_label": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Dealer-facing sentence (es-CL) for `reason`."
                                  },
                                  "qualified_7d": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "threshold": {
                                    "type": "number"
                                  },
                                  "campaign_age_days": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "min_campaign_age_days": {
                                    "type": "number"
                                  },
                                  "checked_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "status",
                                  "reason",
                                  "reason_label",
                                  "qualified_7d",
                                  "threshold",
                                  "campaign_age_days",
                                  "min_campaign_age_days",
                                  "checked_at"
                                ],
                                "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                              },
                              "creative_mix": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "mixed",
                                  "separate"
                                ],
                                "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                              },
                              "build_failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string",
                                    "description": "What happened, in plain words for the dealer."
                                  },
                                  "meta_message": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                  }
                                },
                                "required": [
                                  "code",
                                  "message"
                                ],
                                "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                              }
                            },
                            "required": [
                              "id",
                              "lane",
                              "daily_budget_clp",
                              "destination_url",
                              "messaging_account_id",
                              "optimization_goal",
                              "ladder",
                              "creative_mix",
                              "build_failure"
                            ]
                          }
                        },
                        "offer_snapshot": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "failure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "meta_objects": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "level": {
                                "type": "string",
                                "enum": [
                                  "campaign",
                                  "adset",
                                  "creative",
                                  "ad",
                                  "image",
                                  "video"
                                ]
                              },
                              "role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "parent_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "meta_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "media_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "angle": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "format": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "headline_mode": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "deleted_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "outside_edit_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Set when someone changed the object outside Vitrina; automatic steps skip it."
                              },
                              "outside_edit_fields": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "lane_id",
                              "level",
                              "role",
                              "parent_id",
                              "meta_id",
                              "media_kind",
                              "angle",
                              "format",
                              "headline_mode",
                              "created_on_meta_at",
                              "deleted_on_meta_at",
                              "outside_edit_at",
                              "outside_edit_fields"
                            ]
                          }
                        },
                        "activation": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "status",
                        "requires_review",
                        "reviewed_at",
                        "budget_package",
                        "daily_budget_clp",
                        "geo",
                        "status_changed_at",
                        "launched_at",
                        "created_at",
                        "vehicle_id",
                        "stock_paused_at",
                        "stock_pause_reason",
                        "lanes",
                        "offer_snapshot",
                        "failure",
                        "meta_objects",
                        "activation"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "plantilla": "captacion_consignaciones",
                    "status": "in_review",
                    "requires_review": true,
                    "reviewed_at": null,
                    "budget_package": "recomendado",
                    "daily_budget_clp": 60000,
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "location_types": [
                        "home"
                      ],
                      "meta_region_keys": [
                        "673"
                      ],
                      "label": "RM"
                    },
                    "status_changed_at": "2026-10-26T15:10:00.000Z",
                    "launched_at": null,
                    "vehicle_id": null,
                    "stock_paused_at": null,
                    "stock_pause_reason": null,
                    "created_at": "2026-10-26T15:10:00.000Z",
                    "lanes": [
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                        "lane": "landing",
                        "daily_budget_clp": 30000,
                        "destination_url": "https://www.tuautomotora.cl/vende-tu-auto",
                        "messaging_account_id": null,
                        "optimization_goal": "lead",
                        "ladder": null
                      },
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f602",
                        "lane": "whatsapp",
                        "daily_budget_clp": 30000,
                        "destination_url": null,
                        "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                        "optimization_goal": "conversation",
                        "ladder": null
                      }
                    ],
                    "offer_snapshot": null,
                    "failure": null,
                    "meta_objects": [],
                    "activation": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamientos/{id}/rollback": {
      "post": {
        "summary": "Roll back a Lanzamiento",
        "description": "Deletes, in the background, every Meta object the Lanzamiento created (ads first, campaigns last) and moves it to rolled_back. Only before it is live (draft, paused_ready, in_review, failed); 409 while building or once live. An object someone switched on in Ads Manager since is kept, never forced.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Rollback queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "plantilla": {
                          "type": "string",
                          "enum": [
                            "captacion_consignaciones",
                            "venta_auto"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "building",
                            "paused_ready",
                            "in_review",
                            "live",
                            "failed",
                            "rolled_back"
                          ]
                        },
                        "requires_review": {
                          "type": "boolean",
                          "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                        },
                        "reviewed_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "budget_package": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "daily_budget_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "additionalProperties": {}
                        },
                        "status_changed_at": {
                          "type": "string"
                        },
                        "launched_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "vehicle_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                        },
                        "stock_paused_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                        },
                        "stock_pause_reason": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "sold",
                            "reserved",
                            "inactive",
                            "deleted"
                          ]
                        },
                        "lanes": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane": {
                                "type": "string",
                                "enum": [
                                  "landing",
                                  "whatsapp"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "destination_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "messaging_account_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "optimization_goal": {
                                "type": "string",
                                "enum": [
                                  "lead",
                                  "conversation",
                                  "qualified_seller"
                                ]
                              },
                              "ladder": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "waiting",
                                      "skipped",
                                      "stepping",
                                      "stepped",
                                      "failed"
                                    ],
                                    "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                  },
                                  "reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "reason_label": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Dealer-facing sentence (es-CL) for `reason`."
                                  },
                                  "qualified_7d": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "threshold": {
                                    "type": "number"
                                  },
                                  "campaign_age_days": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "min_campaign_age_days": {
                                    "type": "number"
                                  },
                                  "checked_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "status",
                                  "reason",
                                  "reason_label",
                                  "qualified_7d",
                                  "threshold",
                                  "campaign_age_days",
                                  "min_campaign_age_days",
                                  "checked_at"
                                ],
                                "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                              },
                              "creative_mix": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "mixed",
                                  "separate"
                                ],
                                "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                              },
                              "build_failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string",
                                    "description": "What happened, in plain words for the dealer."
                                  },
                                  "meta_message": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                  }
                                },
                                "required": [
                                  "code",
                                  "message"
                                ],
                                "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                              }
                            },
                            "required": [
                              "id",
                              "lane",
                              "daily_budget_clp",
                              "destination_url",
                              "messaging_account_id",
                              "optimization_goal",
                              "ladder",
                              "creative_mix",
                              "build_failure"
                            ]
                          }
                        },
                        "offer_snapshot": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "failure": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "meta_objects": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "lane_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "level": {
                                "type": "string",
                                "enum": [
                                  "campaign",
                                  "adset",
                                  "creative",
                                  "ad",
                                  "image",
                                  "video"
                                ]
                              },
                              "role": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "parent_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "meta_id": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "media_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "angle": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "format": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "headline_mode": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "deleted_on_meta_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "outside_edit_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Set when someone changed the object outside Vitrina; automatic steps skip it."
                              },
                              "outside_edit_fields": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              }
                            },
                            "required": [
                              "id",
                              "lane_id",
                              "level",
                              "role",
                              "parent_id",
                              "meta_id",
                              "media_kind",
                              "angle",
                              "format",
                              "headline_mode",
                              "created_on_meta_at",
                              "deleted_on_meta_at",
                              "outside_edit_at",
                              "outside_edit_fields"
                            ]
                          }
                        },
                        "activation": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        }
                      },
                      "required": [
                        "id",
                        "plantilla",
                        "status",
                        "requires_review",
                        "reviewed_at",
                        "budget_package",
                        "daily_budget_clp",
                        "geo",
                        "status_changed_at",
                        "launched_at",
                        "created_at",
                        "vehicle_id",
                        "stock_paused_at",
                        "stock_pause_reason",
                        "lanes",
                        "offer_snapshot",
                        "failure",
                        "meta_objects",
                        "activation"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "plantilla": "captacion_consignaciones",
                    "status": "paused_ready",
                    "requires_review": true,
                    "reviewed_at": null,
                    "budget_package": "recomendado",
                    "daily_budget_clp": 60000,
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "location_types": [
                        "home"
                      ],
                      "meta_region_keys": [
                        "673"
                      ],
                      "label": "RM"
                    },
                    "status_changed_at": "2026-10-26T15:10:00.000Z",
                    "launched_at": null,
                    "vehicle_id": null,
                    "stock_paused_at": null,
                    "stock_pause_reason": null,
                    "created_at": "2026-10-26T15:10:00.000Z",
                    "lanes": [
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                        "lane": "landing",
                        "daily_budget_clp": 30000,
                        "destination_url": "https://www.tuautomotora.cl/vende-tu-auto",
                        "messaging_account_id": null,
                        "optimization_goal": "lead",
                        "ladder": null
                      },
                      {
                        "id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f602",
                        "lane": "whatsapp",
                        "daily_budget_clp": 30000,
                        "destination_url": null,
                        "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                        "optimization_goal": "conversation",
                        "ladder": null
                      }
                    ],
                    "offer_snapshot": null,
                    "failure": null,
                    "meta_objects": [],
                    "activation": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/ads/venta/suggestions": {
      "get": {
        "summary": "Suggest stock cars to advertise",
        "description": "Ranks the stock cars that can be advertised (disponible, active, priced, with photos, not already advertised) and says why: aging stock (≥ 45 days), many storefront views but few leads (30 days), a high expected margin (only where the purchase cost is recorded) and niche or premium cars. A signal the tenant has no data for is listed in `signals_unavailable`, never guessed. Also returns the cap of cars running at once.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Ranked suggestions.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "suggestions": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "vehicle": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "make": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "model": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "version": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "year": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "price_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "odometer_value": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "cover_photo": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "photos_count": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "id",
                                  "make",
                                  "model",
                                  "version",
                                  "year",
                                  "price_clp",
                                  "odometer_value",
                                  "cover_photo",
                                  "photos_count"
                                ]
                              },
                              "days_in_stock": {
                                "type": "integer"
                              },
                              "views_30d": {
                                "type": "integer"
                              },
                              "leads_30d": {
                                "type": "integer"
                              },
                              "expected_margin_share": {
                                "type": [
                                  "number",
                                  "null"
                                ],
                                "description": "(price − purchase cost) / price; null = cost not recorded."
                              },
                              "score": {
                                "type": "number"
                              },
                              "reasons": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "code": {
                                      "type": "string",
                                      "enum": [
                                        "aging",
                                        "many_views_few_leads",
                                        "high_margin",
                                        "niche"
                                      ]
                                    },
                                    "label": {
                                      "type": "string",
                                      "description": "Dealer-facing, es-CL."
                                    }
                                  },
                                  "required": [
                                    "code",
                                    "label"
                                  ]
                                },
                                "minItems": 1
                              }
                            },
                            "required": [
                              "vehicle",
                              "days_in_stock",
                              "views_30d",
                              "leads_30d",
                              "expected_margin_share",
                              "score",
                              "reasons"
                            ]
                          }
                        },
                        "signals_unavailable": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "signal": {
                                "type": "string",
                                "enum": [
                                  "aging",
                                  "views",
                                  "margin",
                                  "niche"
                                ]
                              },
                              "reason": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "signal",
                              "reason"
                            ]
                          }
                        },
                        "eligible_without_signal": {
                          "type": "integer"
                        },
                        "cap": {
                          "type": "object",
                          "properties": {
                            "running": {
                              "type": "integer"
                            },
                            "max": {
                              "type": "integer"
                            },
                            "remaining": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "running",
                            "max",
                            "remaining"
                          ]
                        },
                        "default_daily_budget_clp": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "suggestions",
                        "signals_unavailable",
                        "eligible_without_signal",
                        "cap",
                        "default_daily_budget_clp"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "suggestions": [
                      {
                        "vehicle": {
                          "id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                          "make": "Toyota",
                          "model": "RAV4",
                          "version": "2.0 LE 4x2",
                          "year": 2020,
                          "price_clp": 17990000,
                          "odometer_value": 62000,
                          "cover_photo": "https://cdn.example.com/rav4.jpg",
                          "photos_count": 14
                        },
                        "days_in_stock": 71,
                        "views_30d": 48,
                        "leads_30d": 1,
                        "expected_margin_share": null,
                        "score": 101.1,
                        "reasons": [
                          {
                            "code": "aging",
                            "label": "Lleva 71 días en stock"
                          },
                          {
                            "code": "many_views_few_leads",
                            "label": "48 visitas en tu sitio en 30 días y solo 1 consulta"
                          }
                        ]
                      }
                    ],
                    "signals_unavailable": [
                      {
                        "signal": "margin",
                        "reason": "Ningún auto tiene registrado cuánto se pagó por él (costo de compra), así que no podemos estimar el margen."
                      }
                    ],
                    "eligible_without_signal": 6,
                    "cap": {
                      "running": 2,
                      "max": 10,
                      "remaining": 8
                    },
                    "default_daily_budget_clp": 10000
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/venta/preflight": {
      "post": {
        "summary": "Check car ads before creating them",
        "description": "Per car: is it sellable and priced, is it already advertised, is its render set done and at the current price, is the daily budget within bounds and Meta's minimum. For the account: Pages, dataset, WhatsApp number, geo and the spending limit against the total of the cars. Reads only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cars": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "vehicle_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "creative_set_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "A `done` per-car render set of this vehicle (POST /ads/lanzamientos/car-creative-sets, #5881). Required to create; optional in the preflight."
                        },
                        "daily_budget_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 5000,
                          "maximum": 200000,
                          "description": "The car ad set's daily budget, CLP. Default 10 000."
                        }
                      },
                      "required": [
                        "vehicle_id"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The connected WhatsApp the ads open."
                  },
                  "region_codes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(0?[1-9]|1[0-6])$"
                    },
                    "maxItems": 16
                  },
                  "page_id": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "dataset_id": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "cars",
                  "messaging_account_id"
                ]
              },
              "example": {
                "cars": [
                  {
                    "vehicle_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                    "creative_set_id": "2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091",
                    "daily_budget_clp": 10000
                  }
                ],
                "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "cars": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "vehicle_id": {
                                "type": "string"
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "problems": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "code": {
                                      "type": "string"
                                    },
                                    "message": {
                                      "type": "string"
                                    }
                                  },
                                  "required": [
                                    "code",
                                    "message"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "vehicle_id",
                              "daily_budget_clp",
                              "problems"
                            ]
                          }
                        },
                        "daily_total_clp": {
                          "type": "number"
                        },
                        "cap": {
                          "type": "object",
                          "properties": {
                            "running": {
                              "type": "integer"
                            },
                            "max": {
                              "type": "integer"
                            },
                            "remaining": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "running",
                            "max",
                            "remaining"
                          ]
                        },
                        "ad_account": {
                          "type": "object",
                          "properties": {
                            "readable": {
                              "type": "boolean"
                            },
                            "ad_account_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "currency": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "daily_spending_limit_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "limit_status": {
                              "type": "string",
                              "enum": [
                                "known",
                                "none",
                                "unknown"
                              ]
                            },
                            "spend_today_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "exceeds_limit": {
                              "type": [
                                "boolean",
                                "null"
                              ]
                            },
                            "min_daily_budget_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "warning": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "readable",
                            "ad_account_id",
                            "currency",
                            "daily_spending_limit_clp",
                            "limit_status",
                            "spend_today_clp",
                            "exceeds_limit",
                            "min_daily_budget_clp",
                            "warning"
                          ]
                        },
                        "pages": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "page_id": {
                                "type": "string"
                              },
                              "name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "page_id",
                              "name"
                            ]
                          }
                        },
                        "page_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "dataset": {
                          "type": "object",
                          "properties": {
                            "dataset_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "selectable": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "dataset_id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "dataset_id",
                                  "name"
                                ]
                              }
                            }
                          },
                          "required": [
                            "dataset_id",
                            "selectable"
                          ]
                        },
                        "whatsapp": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "messaging_account_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "phone_known": {
                              "type": "boolean"
                            },
                            "page_link": {
                              "type": "string",
                              "enum": [
                                "linked",
                                "not_linked",
                                "unknown"
                              ],
                              "description": "Whether the number is linked to the ad’s Facebook Page. Meta offers no read for it, so `unknown` unless an earlier Lanzamiento got Meta’s answer for this Page and number (`not_linked`: Meta refused the click-to-WhatsApp ad set; `linked`: it built). Never a blocker."
                            },
                            "page_link_warning": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Plain words for the dealer when the link is not known to be there; null when `page_link` is `linked`."
                            }
                          },
                          "required": [
                            "messaging_account_id",
                            "phone_known",
                            "page_link",
                            "page_link_warning"
                          ]
                        },
                        "geo": {
                          "type": "object",
                          "properties": {
                            "region_codes": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              }
                            },
                            "label": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "location_types": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "enum": [
                                  "home"
                                ]
                              }
                            },
                            "supported": {
                              "type": "boolean"
                            }
                          },
                          "required": [
                            "region_codes",
                            "label",
                            "location_types",
                            "supported"
                          ]
                        },
                        "problems": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "cars",
                        "daily_total_clp",
                        "cap",
                        "ad_account",
                        "pages",
                        "page_id",
                        "dataset",
                        "whatsapp",
                        "geo",
                        "problems"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "cars": [
                      {
                        "vehicle_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                        "daily_budget_clp": 10000,
                        "problems": []
                      }
                    ],
                    "daily_total_clp": 10000,
                    "cap": {
                      "running": 2,
                      "max": 10,
                      "remaining": 8
                    },
                    "ad_account": {
                      "readable": true,
                      "ad_account_id": "act_100000000000001",
                      "currency": "CLP",
                      "daily_spending_limit_clp": null,
                      "limit_status": "none",
                      "spend_today_clp": 0,
                      "exceeds_limit": false,
                      "min_daily_budget_clp": 1000,
                      "warning": null
                    },
                    "pages": [
                      {
                        "page_id": "100000000000002",
                        "name": "Tu Automotora"
                      }
                    ],
                    "page_id": "100000000000002",
                    "dataset": {
                      "dataset_id": "100000000000003",
                      "selectable": []
                    },
                    "whatsapp": {
                      "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5",
                      "phone_known": true,
                      "page_link": "linked",
                      "page_link_warning": null
                    },
                    "geo": {
                      "region_codes": [
                        "13"
                      ],
                      "label": "RM",
                      "location_types": [
                        "home"
                      ],
                      "supported": true
                    },
                    "problems": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/venta/lanzamientos": {
      "get": {
        "summary": "List car ads",
        "description": "One row per car Lanzamiento: the car (and whether it is still available), its ad (on, or paused and why: the car was sold, reserved or hidden, or a price change replaced it), whether automatic steps skip it because it was edited outside Vitrina, the latest price-change swap, and the cap.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "building",
                "paused_ready",
                "in_review",
                "live",
                "failed",
                "rolled_back"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The car ads.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "lanzamientos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "requires_review": {
                                "type": "boolean"
                              },
                              "reviewed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "launched_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "stock_paused_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "stock_pause_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {}
                              },
                              "vehicle": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "id": {
                                    "type": "string"
                                  },
                                  "name": {
                                    "type": "string"
                                  },
                                  "price_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "status": {
                                    "type": "string"
                                  },
                                  "available": {
                                    "type": "boolean"
                                  },
                                  "cover_photo": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "id",
                                  "name",
                                  "price_clp",
                                  "status",
                                  "available",
                                  "cover_photo"
                                ]
                              },
                              "ad": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "ad_object_id": {
                                    "type": "string"
                                  },
                                  "meta_id": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "status": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "paused_reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "price_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "automation_skip_reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "ad_object_id",
                                  "meta_id",
                                  "status",
                                  "paused_reason",
                                  "price_clp",
                                  "automation_skip_reason"
                                ]
                              },
                              "last_swap": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "status": {
                                    "type": "string"
                                  },
                                  "skip_reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "from_price_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "to_price_clp": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "created_at": {
                                    "type": "string"
                                  },
                                  "completed_at": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "status",
                                  "skip_reason",
                                  "from_price_clp",
                                  "to_price_clp",
                                  "created_at",
                                  "completed_at"
                                ]
                              }
                            },
                            "required": [
                              "id",
                              "status",
                              "requires_review",
                              "reviewed_at",
                              "daily_budget_clp",
                              "launched_at",
                              "created_at",
                              "stock_paused_at",
                              "stock_pause_reason",
                              "failure",
                              "vehicle",
                              "ad",
                              "last_swap"
                            ]
                          }
                        },
                        "cap": {
                          "type": "object",
                          "properties": {
                            "running": {
                              "type": "integer"
                            },
                            "max": {
                              "type": "integer"
                            },
                            "remaining": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "running",
                            "max",
                            "remaining"
                          ]
                        }
                      },
                      "required": [
                        "lanzamientos",
                        "cap"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "lanzamientos": [],
                    "cap": {
                      "running": 2,
                      "max": 10,
                      "remaining": 8
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Advertise cars (one Lanzamiento per car)",
        "description": "Creates one «Venta de un auto» Lanzamiento per car and builds it on Meta in the background: one ABO campaign with ONE click-to-WhatsApp ad set at the car's daily budget (default 10 000 CLP) and one ad showing the car's 4:5 render. EVERYTHING IS CREATED PAUSED; confirm each with POST /ads/lanzamientos/{id}/confirm (a first Lanzamiento waits for Vitrina's review). The chat the ad opens carries the car: the agent sends its photos and price and books the visit. At most 10 cars run at once per workspace. 400 with `problems` and `cars[].problems` when any car or the account refuses (all or nothing); a car a concurrent request took first is listed in `refused`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "cars": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "vehicle_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "creative_set_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "A `done` per-car render set of this vehicle (POST /ads/lanzamientos/car-creative-sets, #5881). Required to create; optional in the preflight."
                        },
                        "daily_budget_clp": {
                          "type": [
                            "integer",
                            "null"
                          ],
                          "minimum": 5000,
                          "maximum": 200000,
                          "description": "The car ad set's daily budget, CLP. Default 10 000."
                        }
                      },
                      "required": [
                        "vehicle_id"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 10
                  },
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The connected WhatsApp the ads open."
                  },
                  "region_codes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "pattern": "^(0?[1-9]|1[0-6])$"
                    },
                    "maxItems": 16
                  },
                  "page_id": {
                    "type": "string",
                    "maxLength": 40
                  },
                  "dataset_id": {
                    "type": "string",
                    "maxLength": 40
                  }
                },
                "required": [
                  "cars",
                  "messaging_account_id"
                ]
              },
              "example": {
                "cars": [
                  {
                    "vehicle_id": "6a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                    "creative_set_id": "2b3c4d5e-6f70-4812-9a3b-4c5d6e7f8091",
                    "daily_budget_clp": 10000
                  }
                ],
                "messaging_account_id": "7c2d3e4f-5a6b-4c7d-8e9f-a0b1c2d3e4f5"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Building.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "lanzamientos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "plantilla": {
                                "type": "string",
                                "enum": [
                                  "captacion_consignaciones",
                                  "venta_auto"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "draft",
                                  "building",
                                  "paused_ready",
                                  "in_review",
                                  "live",
                                  "failed",
                                  "rolled_back"
                                ]
                              },
                              "requires_review": {
                                "type": "boolean",
                                "description": "The first Lanzamiento of a dealer waits for a Vitrina review."
                              },
                              "reviewed_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "budget_package": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "daily_budget_clp": {
                                "type": [
                                  "number",
                                  "null"
                                ]
                              },
                              "geo": {
                                "type": "object",
                                "additionalProperties": {}
                              },
                              "status_changed_at": {
                                "type": "string"
                              },
                              "launched_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              },
                              "vehicle_id": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "«Venta de un auto» (#5880): the stock car. Null otherwise."
                              },
                              "stock_paused_at": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "«Venta de un auto»: when the car left stock and its ads were paused (never deleted). Null otherwise."
                              },
                              "stock_pause_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "sold",
                                  "reserved",
                                  "inactive",
                                  "deleted"
                                ]
                              },
                              "lanes": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string"
                                    },
                                    "lane": {
                                      "type": "string",
                                      "enum": [
                                        "landing",
                                        "whatsapp"
                                      ]
                                    },
                                    "daily_budget_clp": {
                                      "type": [
                                        "number",
                                        "null"
                                      ]
                                    },
                                    "destination_url": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "messaging_account_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "optimization_goal": {
                                      "type": "string",
                                      "enum": [
                                        "lead",
                                        "conversation",
                                        "qualified_seller"
                                      ]
                                    },
                                    "ladder": {
                                      "type": [
                                        "object",
                                        "null"
                                      ],
                                      "properties": {
                                        "status": {
                                          "type": "string",
                                          "enum": [
                                            "waiting",
                                            "skipped",
                                            "stepping",
                                            "stepped",
                                            "failed"
                                          ],
                                          "description": "waiting: thresholds not met yet; skipped: blocked (see reason); stepping: in progress; stepped: optimizing on the qualified-seller event; failed: the step was undone (see failure)."
                                        },
                                        "reason": {
                                          "type": [
                                            "string",
                                            "null"
                                          ]
                                        },
                                        "reason_label": {
                                          "type": [
                                            "string",
                                            "null"
                                          ],
                                          "description": "Dealer-facing sentence (es-CL) for `reason`."
                                        },
                                        "qualified_7d": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        },
                                        "threshold": {
                                          "type": "number"
                                        },
                                        "campaign_age_days": {
                                          "type": [
                                            "number",
                                            "null"
                                          ]
                                        },
                                        "min_campaign_age_days": {
                                          "type": "number"
                                        },
                                        "checked_at": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "status",
                                        "reason",
                                        "reason_label",
                                        "qualified_7d",
                                        "threshold",
                                        "campaign_age_days",
                                        "min_campaign_age_days",
                                        "checked_at"
                                      ],
                                      "description": "The optimization ladder's last decision for this lane (#5877); null = never evaluated. Extra keys when stepped: stepped_at, adset_object_id, moved_ads, paused[], double_spend_risk; skipped[] lists objects left alone because they were edited outside Vitrina."
                                    },
                                    "creative_mix": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "enum": [
                                        "mixed",
                                        "separate"
                                      ],
                                      "description": "mixed: one ad set with videos beside statics; separate: a video ad set per lane. null = created before the option (separate)."
                                    },
                                    "build_failure": {
                                      "type": [
                                        "object",
                                        "null"
                                      ],
                                      "properties": {
                                        "code": {
                                          "type": "string"
                                        },
                                        "message": {
                                          "type": "string",
                                          "description": "What happened, in plain words for the dealer."
                                        },
                                        "meta_message": {
                                          "type": [
                                            "string",
                                            "null"
                                          ],
                                          "description": "Meta's (or the attribution engine's) own error text, verbatim."
                                        }
                                      },
                                      "required": [
                                        "code",
                                        "message"
                                      ],
                                      "description": "This lane failed to build and was rolled back on its own; the other lane stayed created PAUSED. null = built."
                                    }
                                  },
                                  "required": [
                                    "id",
                                    "lane",
                                    "daily_budget_clp",
                                    "destination_url",
                                    "messaging_account_id",
                                    "optimization_goal",
                                    "ladder",
                                    "creative_mix",
                                    "build_failure"
                                  ]
                                }
                              },
                              "offer_snapshot": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {}
                              },
                              "failure": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {}
                              },
                              "meta_objects": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "id": {
                                      "type": "string"
                                    },
                                    "lane_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "level": {
                                      "type": "string",
                                      "enum": [
                                        "campaign",
                                        "adset",
                                        "creative",
                                        "ad",
                                        "image",
                                        "video"
                                      ]
                                    },
                                    "role": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "parent_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "meta_id": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "media_kind": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "angle": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "format": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "headline_mode": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "created_on_meta_at": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "deleted_on_meta_at": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "outside_edit_at": {
                                      "type": [
                                        "string",
                                        "null"
                                      ],
                                      "description": "Set when someone changed the object outside Vitrina; automatic steps skip it."
                                    },
                                    "outside_edit_fields": {
                                      "type": "array",
                                      "items": {
                                        "type": "string"
                                      }
                                    }
                                  },
                                  "required": [
                                    "id",
                                    "lane_id",
                                    "level",
                                    "role",
                                    "parent_id",
                                    "meta_id",
                                    "media_kind",
                                    "angle",
                                    "format",
                                    "headline_mode",
                                    "created_on_meta_at",
                                    "deleted_on_meta_at",
                                    "outside_edit_at",
                                    "outside_edit_fields"
                                  ]
                                }
                              },
                              "activation": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "additionalProperties": {}
                              }
                            },
                            "required": [
                              "id",
                              "plantilla",
                              "status",
                              "requires_review",
                              "reviewed_at",
                              "budget_package",
                              "daily_budget_clp",
                              "geo",
                              "status_changed_at",
                              "launched_at",
                              "created_at",
                              "vehicle_id",
                              "stock_paused_at",
                              "stock_pause_reason",
                              "lanes",
                              "offer_snapshot",
                              "failure",
                              "meta_objects",
                              "activation"
                            ]
                          }
                        },
                        "refused": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "vehicle_id": {
                                "type": "string"
                              },
                              "code": {
                                "type": "string"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "vehicle_id",
                              "code",
                              "message"
                            ]
                          }
                        }
                      },
                      "required": [
                        "lanzamientos",
                        "refused"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "lanzamientos": [],
                    "refused": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/ads/venta/chileautos-cost": {
      "get": {
        "summary": "What Chileautos costs",
        "description": "The dealer’s Chileautos cost as a dated history: each entry is an amount every N months, in force from its first day until the next entry. The comparison prices each past period with the cost in force then.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The Chileautos cost history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "portal": {
                          "type": "string",
                          "enum": [
                            "chileautos"
                          ]
                        },
                        "current": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "amount_clp": {
                              "type": "integer"
                            },
                            "period_months": {
                              "type": "integer"
                            },
                            "monthly_clp": {
                              "type": "integer",
                              "description": "amount_clp / period_months, rounded."
                            },
                            "effective_from": {
                              "type": "string",
                              "description": "YYYY-MM-DD, first day in force."
                            },
                            "effective_to": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "YYYY-MM-DD, the last day in force; null = still in force."
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "created_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "amount_clp",
                            "period_months",
                            "monthly_clp",
                            "effective_from",
                            "effective_to",
                            "note",
                            "created_at"
                          ],
                          "description": "The entry in force today; null when none is."
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "amount_clp": {
                                "type": "integer"
                              },
                              "period_months": {
                                "type": "integer"
                              },
                              "monthly_clp": {
                                "type": "integer",
                                "description": "amount_clp / period_months, rounded."
                              },
                              "effective_from": {
                                "type": "string",
                                "description": "YYYY-MM-DD, first day in force."
                              },
                              "effective_to": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "YYYY-MM-DD, the last day in force; null = still in force."
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "amount_clp",
                              "period_months",
                              "monthly_clp",
                              "effective_from",
                              "effective_to",
                              "note",
                              "created_at"
                            ]
                          },
                          "description": "The whole history, newest first."
                        }
                      },
                      "required": [
                        "portal",
                        "current",
                        "entries"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "portal": "chileautos",
                    "current": {
                      "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                      "amount_clp": 600000,
                      "period_months": 2,
                      "monthly_clp": 300000,
                      "effective_from": "2026-09-01",
                      "effective_to": null,
                      "note": "Plan bimestral",
                      "created_at": "2026-10-27T15:00:00.000Z"
                    },
                    "entries": [
                      {
                        "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                        "amount_clp": 600000,
                        "period_months": 2,
                        "monthly_clp": 300000,
                        "effective_from": "2026-09-01",
                        "effective_to": null,
                        "note": "Plan bimestral",
                        "created_at": "2026-10-27T15:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Record what Chileautos costs",
        "description": "Records the Chileautos cost from a day on. A price change is a new entry with a later effective_from; the same effective_from corrects that entry. amount_clp 0 records that the dealer stopped paying.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_clp": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000000000,
                    "description": "Whole CLP the dealer pays Chileautos per period. 0 records that the dealer stopped paying."
                  },
                  "period_months": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12,
                    "description": "How many months that amount covers (e.g. 2 for a bimonthly plan)."
                  },
                  "effective_from": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "First day (America/Santiago) this cost is in force. The previous entry ends the day before; the same day again corrects that entry."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 300
                  }
                },
                "required": [
                  "amount_clp",
                  "period_months",
                  "effective_from"
                ]
              },
              "example": {
                "amount_clp": 600000,
                "period_months": 2,
                "effective_from": "2026-09-01"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The Chileautos cost history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "portal": {
                          "type": "string",
                          "enum": [
                            "chileautos"
                          ]
                        },
                        "current": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "amount_clp": {
                              "type": "integer"
                            },
                            "period_months": {
                              "type": "integer"
                            },
                            "monthly_clp": {
                              "type": "integer",
                              "description": "amount_clp / period_months, rounded."
                            },
                            "effective_from": {
                              "type": "string",
                              "description": "YYYY-MM-DD, first day in force."
                            },
                            "effective_to": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "YYYY-MM-DD, the last day in force; null = still in force."
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "created_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "amount_clp",
                            "period_months",
                            "monthly_clp",
                            "effective_from",
                            "effective_to",
                            "note",
                            "created_at"
                          ],
                          "description": "The entry in force today; null when none is."
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "amount_clp": {
                                "type": "integer"
                              },
                              "period_months": {
                                "type": "integer"
                              },
                              "monthly_clp": {
                                "type": "integer",
                                "description": "amount_clp / period_months, rounded."
                              },
                              "effective_from": {
                                "type": "string",
                                "description": "YYYY-MM-DD, first day in force."
                              },
                              "effective_to": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "YYYY-MM-DD, the last day in force; null = still in force."
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "amount_clp",
                              "period_months",
                              "monthly_clp",
                              "effective_from",
                              "effective_to",
                              "note",
                              "created_at"
                            ]
                          },
                          "description": "The whole history, newest first."
                        }
                      },
                      "required": [
                        "portal",
                        "current",
                        "entries"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "portal": "chileautos",
                    "current": {
                      "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                      "amount_clp": 600000,
                      "period_months": 2,
                      "monthly_clp": 300000,
                      "effective_from": "2026-09-01",
                      "effective_to": null,
                      "note": "Plan bimestral",
                      "created_at": "2026-10-27T15:00:00.000Z"
                    },
                    "entries": [
                      {
                        "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                        "amount_clp": 600000,
                        "period_months": 2,
                        "monthly_clp": 300000,
                        "effective_from": "2026-09-01",
                        "effective_to": null,
                        "note": "Plan bimestral",
                        "created_at": "2026-10-27T15:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/venta/chileautos-cost/{id}": {
      "delete": {
        "summary": "Remove a Chileautos cost entry",
        "description": "Removes one mistaken entry; the previous entry then stays in force until the next one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Chileautos cost history.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "portal": {
                          "type": "string",
                          "enum": [
                            "chileautos"
                          ]
                        },
                        "current": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "amount_clp": {
                              "type": "integer"
                            },
                            "period_months": {
                              "type": "integer"
                            },
                            "monthly_clp": {
                              "type": "integer",
                              "description": "amount_clp / period_months, rounded."
                            },
                            "effective_from": {
                              "type": "string",
                              "description": "YYYY-MM-DD, first day in force."
                            },
                            "effective_to": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "YYYY-MM-DD, the last day in force; null = still in force."
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "created_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "amount_clp",
                            "period_months",
                            "monthly_clp",
                            "effective_from",
                            "effective_to",
                            "note",
                            "created_at"
                          ],
                          "description": "The entry in force today; null when none is."
                        },
                        "entries": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "amount_clp": {
                                "type": "integer"
                              },
                              "period_months": {
                                "type": "integer"
                              },
                              "monthly_clp": {
                                "type": "integer",
                                "description": "amount_clp / period_months, rounded."
                              },
                              "effective_from": {
                                "type": "string",
                                "description": "YYYY-MM-DD, first day in force."
                              },
                              "effective_to": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "YYYY-MM-DD, the last day in force; null = still in force."
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "created_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "amount_clp",
                              "period_months",
                              "monthly_clp",
                              "effective_from",
                              "effective_to",
                              "note",
                              "created_at"
                            ]
                          },
                          "description": "The whole history, newest first."
                        }
                      },
                      "required": [
                        "portal",
                        "current",
                        "entries"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "portal": "chileautos",
                    "current": {
                      "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                      "amount_clp": 600000,
                      "period_months": 2,
                      "monthly_clp": 300000,
                      "effective_from": "2026-09-01",
                      "effective_to": null,
                      "note": "Plan bimestral",
                      "created_at": "2026-10-27T15:00:00.000Z"
                    },
                    "entries": [
                      {
                        "id": "2b1f5c8e-6c1d-4f7a-9d55-0f3e2a9b7c41",
                        "amount_clp": 600000,
                        "period_months": 2,
                        "monthly_clp": 300000,
                        "effective_from": "2026-09-01",
                        "effective_to": null,
                        "note": "Plan bimestral",
                        "created_at": "2026-10-27T15:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/venta/comparison": {
      "get": {
        "summary": "Meta car ads vs Chileautos",
        "description": "Cost per lead, per visit booked and per sale of Chileautos (the entered cost, prorated per day) against Meta car ads («Venta de un auto» Lanzamientos, ad spend) over the same period, and for the cars that had both. Each step counts what happened in the period. A cost is null when unknown and a cost per step is null when its count is zero; counts are never null. Until a «Venta de un auto» Lanzamiento exists the Meta side reads `no_lanzamientos` with no funnel.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago."
            },
            "required": true,
            "description": "Window start (inclusive), YYYY-MM-DD, America/Santiago.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`."
            },
            "required": true,
            "description": "Window end (inclusive), YYYY-MM-DD, America/Santiago. At most 366 days after `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The comparison for the period.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "object",
                          "properties": {
                            "from": {
                              "type": "string"
                            },
                            "to": {
                              "type": "string"
                            },
                            "timezone": {
                              "type": "string",
                              "enum": [
                                "America/Santiago"
                              ]
                            }
                          },
                          "required": [
                            "from",
                            "to",
                            "timezone"
                          ]
                        },
                        "chileautos": {
                          "type": "object",
                          "properties": {
                            "cost_status": {
                              "type": "string",
                              "enum": [
                                "ok",
                                "not_entered",
                                "partial"
                              ],
                              "description": "not_entered = no cost entered; partial = part of the period precedes the first entry (cost null)."
                            },
                            "cost_from": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The first day any cost is in force."
                            },
                            "funnel": {
                              "type": "object",
                              "properties": {
                                "cost": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "CLP in the period. Chileautos: the entered cost prorated per day; Meta: ad spend. null = unknown, never 0."
                                },
                                "leads": {
                                  "type": "integer",
                                  "description": "Leads created in the period."
                                },
                                "visits_booked": {
                                  "type": "integer",
                                  "description": "Leads whose first visit or test drive was booked in the period."
                                },
                                "sales": {
                                  "type": "integer",
                                  "description": "Cars sold in the period whose sale is credited to a lead of this channel."
                                },
                                "cost_per_lead": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "null when the cost is unknown or there are no leads."
                                },
                                "cost_per_visit": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "cost_per_sale": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "cost",
                                "leads",
                                "visits_booked",
                                "sales",
                                "cost_per_lead",
                                "cost_per_visit",
                                "cost_per_sale"
                              ]
                            }
                          },
                          "required": [
                            "cost_status",
                            "cost_from",
                            "funnel"
                          ]
                        },
                        "meta": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "ok",
                                "no_lanzamientos"
                              ],
                              "description": "no_lanzamientos = no «Venta de un auto» Lanzamiento yet: no funnel."
                            },
                            "spend_status": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "ok",
                                "unavailable",
                                "not_reported"
                              ],
                              "description": "unavailable = the spend read failed; not_reported = no spend reported for these ads. null with no_lanzamientos."
                            },
                            "lanzamientos": {
                              "type": "integer"
                            },
                            "funnel": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "cost": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "CLP in the period. Chileautos: the entered cost prorated per day; Meta: ad spend. null = unknown, never 0."
                                },
                                "leads": {
                                  "type": "integer",
                                  "description": "Leads created in the period."
                                },
                                "visits_booked": {
                                  "type": "integer",
                                  "description": "Leads whose first visit or test drive was booked in the period."
                                },
                                "sales": {
                                  "type": "integer",
                                  "description": "Cars sold in the period whose sale is credited to a lead of this channel."
                                },
                                "cost_per_lead": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "null when the cost is unknown or there are no leads."
                                },
                                "cost_per_visit": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "cost_per_sale": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "cost",
                                "leads",
                                "visits_booked",
                                "sales",
                                "cost_per_lead",
                                "cost_per_visit",
                                "cost_per_sale"
                              ]
                            }
                          },
                          "required": [
                            "status",
                            "spend_status",
                            "lanzamientos",
                            "funnel"
                          ]
                        },
                        "same_cars": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "ok",
                                "no_lanzamientos",
                                "no_overlap"
                              ]
                            },
                            "vehicle_ids": {
                              "type": "array",
                              "items": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "description": "Cars listed on Chileautos AND with a Meta car ad in the period."
                            },
                            "chileautos_listed_cars": {
                              "type": "integer",
                              "description": "Cars listed on Chileautos in the period."
                            },
                            "chileautos": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "cost": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "CLP in the period. Chileautos: the entered cost prorated per day; Meta: ad spend. null = unknown, never 0."
                                },
                                "leads": {
                                  "type": "integer",
                                  "description": "Leads created in the period."
                                },
                                "visits_booked": {
                                  "type": "integer",
                                  "description": "Leads whose first visit or test drive was booked in the period."
                                },
                                "sales": {
                                  "type": "integer",
                                  "description": "Cars sold in the period whose sale is credited to a lead of this channel."
                                },
                                "cost_per_lead": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "null when the cost is unknown or there are no leads."
                                },
                                "cost_per_visit": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "cost_per_sale": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "cost",
                                "leads",
                                "visits_booked",
                                "sales",
                                "cost_per_lead",
                                "cost_per_visit",
                                "cost_per_sale"
                              ],
                              "description": "Those cars on Chileautos; cost = the Chileautos cost shared per listed car."
                            },
                            "meta": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "cost": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "CLP in the period. Chileautos: the entered cost prorated per day; Meta: ad spend. null = unknown, never 0."
                                },
                                "leads": {
                                  "type": "integer",
                                  "description": "Leads created in the period."
                                },
                                "visits_booked": {
                                  "type": "integer",
                                  "description": "Leads whose first visit or test drive was booked in the period."
                                },
                                "sales": {
                                  "type": "integer",
                                  "description": "Cars sold in the period whose sale is credited to a lead of this channel."
                                },
                                "cost_per_lead": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "null when the cost is unknown or there are no leads."
                                },
                                "cost_per_visit": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "cost_per_sale": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "cost",
                                "leads",
                                "visits_booked",
                                "sales",
                                "cost_per_lead",
                                "cost_per_visit",
                                "cost_per_sale"
                              ]
                            }
                          },
                          "required": [
                            "status",
                            "vehicle_ids",
                            "chileautos_listed_cars",
                            "chileautos",
                            "meta"
                          ]
                        },
                        "sold_cars": {
                          "type": "integer",
                          "description": "Cars marked sold in the period."
                        },
                        "sold_cars_unattributed": {
                          "type": "integer",
                          "description": "…of which no lead is credited (no sale note, no won lead on the car)."
                        }
                      },
                      "required": [
                        "period",
                        "chileautos",
                        "meta",
                        "same_cars",
                        "sold_cars",
                        "sold_cars_unattributed"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "period": {
                      "from": "2026-10-01",
                      "to": "2026-10-31",
                      "timezone": "America/Santiago"
                    },
                    "chileautos": {
                      "cost_status": "ok",
                      "cost_from": "2026-09-01",
                      "funnel": {
                        "cost": 300000,
                        "leads": 42,
                        "visits_booked": 9,
                        "sales": 2,
                        "cost_per_lead": 7143,
                        "cost_per_visit": 33333,
                        "cost_per_sale": 150000
                      }
                    },
                    "meta": {
                      "status": "no_lanzamientos",
                      "spend_status": null,
                      "lanzamientos": 0,
                      "funnel": null
                    },
                    "same_cars": {
                      "status": "no_lanzamientos",
                      "vehicle_ids": [],
                      "chileautos_listed_cars": 18,
                      "chileautos": null,
                      "meta": null
                    },
                    "sold_cars": 5,
                    "sold_cars_unattributed": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamiento-proposals": {
      "get": {
        "summary": "List fatigue proposals",
        "description": "Fresh creative variants Vitrina rendered for tired Lanzamiento ads: cost per qualified seller at 2x its own baseline, or a 7-day frequency of 4 or more. Never applied by itself: the dealer accepts or dismisses. Status rendering → ready → accepting → accepted; dismissed; failed (see error). Newest first.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "rendering",
                "ready",
                "accepting",
                "accepted",
                "dismissed",
                "failed"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lanzamiento_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The proposals.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string"
                          },
                          "lanzamiento_id": {
                            "type": "string"
                          },
                          "lane_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "ad_object_id": {
                            "type": "string",
                            "description": "The tired ad (a meta_objects id of the Lanzamiento)."
                          },
                          "meta_ad_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "rendering",
                              "ready",
                              "accepting",
                              "accepted",
                              "dismissed",
                              "failed"
                            ]
                          },
                          "reason": {
                            "type": "object",
                            "additionalProperties": {},
                            "description": "Why the ad is tired: signals ['cost'|'frequency'], cost_per_qualified_7d, baseline_cost_per_qualified, spend_7d, qualified_7d, frequency_7d (CLP)."
                          },
                          "angles": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "creative_set_id": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "variants": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "feed_id": {
                                  "type": "string"
                                },
                                "vertical_id": {
                                  "type": "string"
                                },
                                "angle": {
                                  "type": "string"
                                },
                                "variant": {
                                  "type": "string"
                                },
                                "feed_url": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "vertical_url": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "feed_id",
                                "vertical_id",
                                "angle",
                                "variant",
                                "feed_url",
                                "vertical_url"
                              ]
                            }
                          },
                          "new_ad_object_ids": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            }
                          },
                          "tired_ad_paused": {
                            "type": [
                              "boolean",
                              "null"
                            ]
                          },
                          "error": {
                            "type": [
                              "object",
                              "null"
                            ],
                            "additionalProperties": {}
                          },
                          "decided_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "updated_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "lanzamiento_id",
                          "lane_id",
                          "ad_object_id",
                          "meta_ad_id",
                          "status",
                          "reason",
                          "angles",
                          "creative_set_id",
                          "variants",
                          "new_ad_object_ids",
                          "tired_ad_paused",
                          "error",
                          "decided_at",
                          "created_at",
                          "updated_at"
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                      "lanzamiento_id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                      "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                      "ad_object_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
                      "meta_ad_id": "120211111111111111",
                      "status": "ready",
                      "reason": {
                        "signals": [
                          "cost"
                        ],
                        "cost_per_qualified_7d": 18000,
                        "baseline_cost_per_qualified": 8000,
                        "spend_7d": 72000,
                        "qualified_7d": 4,
                        "frequency_7d": 2.6
                      },
                      "angles": [
                        "fotos",
                        "opciones"
                      ],
                      "creative_set_id": "6b5a4f3e-2d1c-4b0a-9f8e-7d6c5b4a3f2e",
                      "variants": [
                        {
                          "feed_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                          "vertical_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
                          "angle": "fotos",
                          "variant": "default",
                          "feed_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1350.png",
                          "vertical_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1920.png"
                        }
                      ],
                      "new_ad_object_ids": [],
                      "tired_ad_paused": null,
                      "error": null,
                      "decided_at": null,
                      "created_at": "2026-11-10T12:00:00.000Z",
                      "updated_at": "2026-11-10T12:01:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamiento-proposals/{id}": {
      "get": {
        "summary": "Get a fatigue proposal",
        "description": "One fatigue proposal with its fresh variants.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The proposal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "lanzamiento_id": {
                          "type": "string"
                        },
                        "lane_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "ad_object_id": {
                          "type": "string",
                          "description": "The tired ad (a meta_objects id of the Lanzamiento)."
                        },
                        "meta_ad_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "rendering",
                            "ready",
                            "accepting",
                            "accepted",
                            "dismissed",
                            "failed"
                          ]
                        },
                        "reason": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "Why the ad is tired: signals ['cost'|'frequency'], cost_per_qualified_7d, baseline_cost_per_qualified, spend_7d, qualified_7d, frequency_7d (CLP)."
                        },
                        "angles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "creative_set_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "variants": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "feed_id": {
                                "type": "string"
                              },
                              "vertical_id": {
                                "type": "string"
                              },
                              "angle": {
                                "type": "string"
                              },
                              "variant": {
                                "type": "string"
                              },
                              "feed_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "vertical_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "feed_id",
                              "vertical_id",
                              "angle",
                              "variant",
                              "feed_url",
                              "vertical_url"
                            ]
                          }
                        },
                        "new_ad_object_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "tired_ad_paused": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "error": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "decided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "lanzamiento_id",
                        "lane_id",
                        "ad_object_id",
                        "meta_ad_id",
                        "status",
                        "reason",
                        "angles",
                        "creative_set_id",
                        "variants",
                        "new_ad_object_ids",
                        "tired_ad_paused",
                        "error",
                        "decided_at",
                        "created_at",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "lanzamiento_id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                    "ad_object_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
                    "meta_ad_id": "120211111111111111",
                    "status": "ready",
                    "reason": {
                      "signals": [
                        "cost"
                      ],
                      "cost_per_qualified_7d": 18000,
                      "baseline_cost_per_qualified": 8000,
                      "spend_7d": 72000,
                      "qualified_7d": 4,
                      "frequency_7d": 2.6
                    },
                    "angles": [
                      "fotos",
                      "opciones"
                    ],
                    "creative_set_id": "6b5a4f3e-2d1c-4b0a-9f8e-7d6c5b4a3f2e",
                    "variants": [
                      {
                        "feed_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                        "vertical_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
                        "angle": "fotos",
                        "variant": "default",
                        "feed_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1350.png",
                        "vertical_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1920.png"
                      }
                    ],
                    "new_ad_object_ids": [],
                    "tired_ad_paused": null,
                    "error": null,
                    "decided_at": null,
                    "created_at": "2026-11-10T12:00:00.000Z",
                    "updated_at": "2026-11-10T12:01:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamiento-proposals/{id}/accept": {
      "post": {
        "summary": "Accept a fatigue proposal",
        "description": "One click: in the background the fresh variants become new ads in the tired ad's ad set, they are switched on, and only then the tired ad is paused. 409 proposal_not_ready unless ready; 409 lanzamiento_not_live; 409 edited_outside_vitrina / deleted_on_meta when the ad was changed outside Vitrina. Poll the proposal until accepted or failed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted; the Meta work runs in the background.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "lanzamiento_id": {
                          "type": "string"
                        },
                        "lane_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "ad_object_id": {
                          "type": "string",
                          "description": "The tired ad (a meta_objects id of the Lanzamiento)."
                        },
                        "meta_ad_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "rendering",
                            "ready",
                            "accepting",
                            "accepted",
                            "dismissed",
                            "failed"
                          ]
                        },
                        "reason": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "Why the ad is tired: signals ['cost'|'frequency'], cost_per_qualified_7d, baseline_cost_per_qualified, spend_7d, qualified_7d, frequency_7d (CLP)."
                        },
                        "angles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "creative_set_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "variants": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "feed_id": {
                                "type": "string"
                              },
                              "vertical_id": {
                                "type": "string"
                              },
                              "angle": {
                                "type": "string"
                              },
                              "variant": {
                                "type": "string"
                              },
                              "feed_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "vertical_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "feed_id",
                              "vertical_id",
                              "angle",
                              "variant",
                              "feed_url",
                              "vertical_url"
                            ]
                          }
                        },
                        "new_ad_object_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "tired_ad_paused": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "error": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "decided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "lanzamiento_id",
                        "lane_id",
                        "ad_object_id",
                        "meta_ad_id",
                        "status",
                        "reason",
                        "angles",
                        "creative_set_id",
                        "variants",
                        "new_ad_object_ids",
                        "tired_ad_paused",
                        "error",
                        "decided_at",
                        "created_at",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "lanzamiento_id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                    "ad_object_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
                    "meta_ad_id": "120211111111111111",
                    "status": "accepting",
                    "reason": {
                      "signals": [
                        "cost"
                      ],
                      "cost_per_qualified_7d": 18000,
                      "baseline_cost_per_qualified": 8000,
                      "spend_7d": 72000,
                      "qualified_7d": 4,
                      "frequency_7d": 2.6
                    },
                    "angles": [
                      "fotos",
                      "opciones"
                    ],
                    "creative_set_id": "6b5a4f3e-2d1c-4b0a-9f8e-7d6c5b4a3f2e",
                    "variants": [
                      {
                        "feed_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                        "vertical_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
                        "angle": "fotos",
                        "variant": "default",
                        "feed_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1350.png",
                        "vertical_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1920.png"
                      }
                    ],
                    "new_ad_object_ids": [],
                    "tired_ad_paused": null,
                    "error": null,
                    "decided_at": "2026-11-10T13:00:00.000Z",
                    "created_at": "2026-11-10T12:00:00.000Z",
                    "updated_at": "2026-11-10T12:01:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/lanzamiento-proposals/{id}/dismiss": {
      "post": {
        "summary": "Dismiss a fatigue proposal",
        "description": "Leaves the ad as it is. Vitrina does not propose again for that ad for 14 days. 409 proposal_decided once accepted or already dismissed.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The dismissed proposal.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "lanzamiento_id": {
                          "type": "string"
                        },
                        "lane_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "ad_object_id": {
                          "type": "string",
                          "description": "The tired ad (a meta_objects id of the Lanzamiento)."
                        },
                        "meta_ad_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "rendering",
                            "ready",
                            "accepting",
                            "accepted",
                            "dismissed",
                            "failed"
                          ]
                        },
                        "reason": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "Why the ad is tired: signals ['cost'|'frequency'], cost_per_qualified_7d, baseline_cost_per_qualified, spend_7d, qualified_7d, frequency_7d (CLP)."
                        },
                        "angles": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "creative_set_id": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "variants": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "feed_id": {
                                "type": "string"
                              },
                              "vertical_id": {
                                "type": "string"
                              },
                              "angle": {
                                "type": "string"
                              },
                              "variant": {
                                "type": "string"
                              },
                              "feed_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "vertical_url": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "feed_id",
                              "vertical_id",
                              "angle",
                              "variant",
                              "feed_url",
                              "vertical_url"
                            ]
                          }
                        },
                        "new_ad_object_ids": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "tired_ad_paused": {
                          "type": [
                            "boolean",
                            "null"
                          ]
                        },
                        "error": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "additionalProperties": {}
                        },
                        "decided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "lanzamiento_id",
                        "lane_id",
                        "ad_object_id",
                        "meta_ad_id",
                        "status",
                        "reason",
                        "angles",
                        "creative_set_id",
                        "variants",
                        "new_ad_object_ids",
                        "tired_ad_paused",
                        "error",
                        "decided_at",
                        "created_at",
                        "updated_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "3f2a1b0c-9d8e-4f7a-8b6c-5d4e3f2a1b0c",
                    "lanzamiento_id": "0b6f4c1a-6a9e-4f0e-8a51-3f1d2c7e9a10",
                    "lane_id": "5a1e2b3c-4d5e-4f60-8a71-b2c3d4e5f601",
                    "ad_object_id": "9e8d7c6b-5a4f-4e3d-8c2b-1a0f9e8d7c6b",
                    "meta_ad_id": "120211111111111111",
                    "status": "dismissed",
                    "reason": {
                      "signals": [
                        "cost"
                      ],
                      "cost_per_qualified_7d": 18000,
                      "baseline_cost_per_qualified": 8000,
                      "spend_7d": 72000,
                      "qualified_7d": 4,
                      "frequency_7d": 2.6
                    },
                    "angles": [
                      "fotos",
                      "opciones"
                    ],
                    "creative_set_id": "6b5a4f3e-2d1c-4b0a-9f8e-7d6c5b4a3f2e",
                    "variants": [
                      {
                        "feed_id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
                        "vertical_id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
                        "angle": "fotos",
                        "variant": "default",
                        "feed_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1350.png",
                        "vertical_url": "https://media.vitrina.cl/t/other/lanzamiento-set-fotos_1080x1920.png"
                      }
                    ],
                    "new_ad_object_ids": [],
                    "tired_ad_paused": null,
                    "error": null,
                    "decided_at": "2026-11-10T13:00:00.000Z",
                    "created_at": "2026-11-10T12:00:00.000Z",
                    "updated_at": "2026-11-10T12:01:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/ads/audiences/customer-list": {
      "get": {
        "summary": "The paying-customers audience: state and consent",
        "description": "A Meta customer list named «Vitrina · Clientes que pagaron»: the people who paid in the last year, as hashed e-mail, phone, first name, last name and country (never RUT, amounts, treatments or clinical data; a paying guardian is sent as the guardian), marketing opt-outs and minors with a known birth date always excluded, refreshed daily by the measurement engine; usable in Meta for a 1 % lookalike (optional), exclusion or retargeting. Google Customer Match is never used for clinics. The read is also the capability check: `coming_soon` until the engine can build it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The audience state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAudienceStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "live",
                    "reason": null,
                    "names": {
                      "audience": "Vitrina · Clientes que pagaron",
                      "lookalike": "Vitrina · Parecidos a clientes que pagaron (1 %)"
                    },
                    "size": {
                      "lower": 1000,
                      "upper": 1200
                    },
                    "last_refreshed_at": "2026-10-03T06:40:00.000Z",
                    "lookalike": {
                      "state": "ready",
                      "ratio": 0.01
                    },
                    "optin": {
                      "accepted_at": "2026-10-01T21:10:00.000Z",
                      "consent_version": "2026-10-04.customer-list.v2",
                      "current": true,
                      "lookalike": true
                    },
                    "consent": {
                      "version": "2026-10-04.customer-list.v2",
                      "text": "Vitrina creará en tu cuenta de Meta una lista llamada «Vitrina · Clientes que pagaron»…",
                      "text_hash": "0000000000000000000000000000000000000000000000000000000000000000"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Turn on the paying-customers audience",
        "description": "A Meta customer list named «Vitrina · Clientes que pagaron»: the people who paid in the last year, as hashed e-mail, phone, first name, last name and country (never RUT, amounts, treatments or clinical data; a paying guardian is sent as the guardian), marketing opt-outs and minors with a known birth date always excluded, refreshed daily by the measurement engine; usable in Meta for a 1 % lookalike (optional), exclusion or retargeting. Google Customer Match is never used for clinics. Needs the CURRENT consent (`consent.version` + `consent.text_hash` from the read); 409 otherwise, or while the engine cannot build it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "consent_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "consent_text_hash": {
                    "type": "string",
                    "pattern": "^[0-9a-f]{64}$"
                  },
                  "lookalike": {
                    "type": "boolean",
                    "default": true
                  }
                },
                "required": [
                  "consent_version",
                  "consent_text_hash"
                ],
                "additionalProperties": false
              },
              "example": {
                "consent_version": "2026-10-04.customer-list.v2",
                "consent_text_hash": "0000000000000000000000000000000000000000000000000000000000000000",
                "lookalike": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The audience state after turning it on.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAudienceStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "live",
                    "reason": null,
                    "names": {
                      "audience": "Vitrina · Clientes que pagaron",
                      "lookalike": "Vitrina · Parecidos a clientes que pagaron (1 %)"
                    },
                    "size": {
                      "lower": 1000,
                      "upper": 1200
                    },
                    "last_refreshed_at": "2026-10-03T06:40:00.000Z",
                    "lookalike": {
                      "state": "ready",
                      "ratio": 0.01
                    },
                    "optin": {
                      "accepted_at": "2026-10-01T21:10:00.000Z",
                      "consent_version": "2026-10-04.customer-list.v2",
                      "current": true,
                      "lookalike": true
                    },
                    "consent": {
                      "version": "2026-10-04.customer-list.v2",
                      "text": "Vitrina creará en tu cuenta de Meta una lista llamada «Vitrina · Clientes que pagaron»…",
                      "text_hash": "0000000000000000000000000000000000000000000000000000000000000000"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      },
      "delete": {
        "summary": "Turn off the paying-customers audience",
        "description": "Stops the refresh and deletes the list and its lookalike from the Meta account. The owner’s decision is kept (who, when).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Ads"
        ],
        "responses": {
          "200": {
            "description": "The audience state after turning it off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AdsAudienceStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "state": "live",
                    "reason": null,
                    "names": {
                      "audience": "Vitrina · Clientes que pagaron",
                      "lookalike": "Vitrina · Parecidos a clientes que pagaron (1 %)"
                    },
                    "size": {
                      "lower": 1000,
                      "upper": 1200
                    },
                    "last_refreshed_at": "2026-10-03T06:40:00.000Z",
                    "lookalike": {
                      "state": "ready",
                      "ratio": 0.01
                    },
                    "optin": {
                      "accepted_at": "2026-10-01T21:10:00.000Z",
                      "consent_version": "2026-10-04.customer-list.v2",
                      "current": true,
                      "lookalike": true
                    },
                    "consent": {
                      "version": "2026-10-04.customer-list.v2",
                      "text": "Vitrina creará en tu cuenta de Meta una lista llamada «Vitrina · Clientes que pagaron»…",
                      "text_hash": "0000000000000000000000000000000000000000000000000000000000000000"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "Vitrina Ads is not active for this workspace. `error.code` is `ENTITLEMENT_NOT_ACTIVE`, `error.details.feature` is `vitrina_ads`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ENTITLEMENT_NOT_ACTIVE",
                    "message": "El complemento Vitrina Ads no está activo en este espacio de trabajo.",
                    "details": {
                      "required": [
                        "vitrina_ads"
                      ],
                      "active": [],
                      "feature": "vitrina_ads",
                      "entitlement_state": "off"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Vitrina Ads is active but the delegated key has not finished rotating (`ADS_KEY_NEEDS_REMINT`) — retry once `GET /ads/state` reports `needs_remint: false`. Also the generic conflict of a write.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "ADS_KEY_NEEDS_REMINT",
                    "message": "Vitrina Ads is active but the delegated key has not finished rotating (key_kind: core)",
                    "details": {
                      "key_kind": "core"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb-files": {
      "get": {
        "summary": "List the workspace’s knowledge files",
        "description": "Every file with its ingestion `status`, size and content type, plus the agents each one is attached to — so this single call answers both \"what do we have\" and \"who can see it\" without a per-file lookup. Unpaginated, with a `total`.\n\n`status` is the INGESTION state, not an upload state: a file is `ready` the moment its bytes land, before it has been chunked or embedded. A `ready` file is not necessarily retrievable by the agent yet.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "responses": {
          "200": {
            "description": "Knowledge files with their agent attachments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a6a6a6a6-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Garantías 2026.pdf",
                      "content_type": "application/pdf",
                      "size_bytes": 182304,
                      "status": "ingested",
                      "agents": [
                        {
                          "id": "a3a3a3a3-0000-4000-8000-000000000001",
                          "name": "Asistente de ventas"
                        }
                      ],
                      "created_at": "2026-09-01T13:00:00.000Z",
                      "updated_at": "2026-09-01T13:05:00.000Z",
                      "deleted_at": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a knowledge file",
        "description": "A **`multipart/form-data`** request with the document in a field named `file`. The filename and content type come from the part itself. Capped at 25 MB (413 above it); an empty file and a missing part are both 400s.\n\n**Or a JSON body** (`application/json`): `{ filename, content, content_encoding?, content_type? }`, with `content` as UTF-8 text by default or base64 when `content_encoding` is `\"base64\"` (PDF, DOCX, images). The 25 MB cap applies to the decoded bytes; invalid base64 is a 400. The content type defaults from the filename extension. This is the shape an MCP connector sends through `call_operation`, which cannot build multipart. The JSON body may also carry `attach_to_agent_id` to attach the file to that agent in the same call, which needs `ai_agents:write` as well.\n\nStorage and the database row are written together — if the row fails the stored object is removed, so there are no orphans. Ingestion is then enqueued **best-effort**: a 201 means the file is stored, not that it is searchable. Poll `status` on the list, and use `/{id}/reingest` if it never leaves `ready`.\n\nUploading here puts the file in the library and attaches it to NO agent (unless the JSON body names `attach_to_agent_id`). To upload straight onto an agent with multipart, post to `/ai-agents/{id}/knowledge` instead. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The document, at most 25 MB"
                  }
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension. The parser picks md/txt/csv/json/pdf/docx by it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The document. UTF-8 text by default; base64 when `content_encoding` is \"base64\". At most 25 MB once decoded."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for PDF / DOCX / images."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension: .md → text/markdown, .pdf → application/pdf, …"
                  },
                  "attach_to_agent_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Attach to this agent in the same call. Needs `ai_agents:write` as well; the agent must belong to your workspace."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ],
                "additionalProperties": false
              },
              "example": {
                "filename": "precios.md",
                "content": "# Precios 2026\n\nLimpieza dental: $35.000",
                "content_encoding": "utf8"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The stored file row",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a6a6a6a6-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Garantías 2026.pdf",
                    "content_type": "application/pdf",
                    "size_bytes": 182304,
                    "status": "ready",
                    "agents": [
                      {
                        "id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "name": "Asistente de ventas"
                      }
                    ],
                    "created_at": "2026-09-01T13:00:00.000Z",
                    "updated_at": "2026-09-01T13:05:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/kb-files/{id}/text": {
      "get": {
        "tags": [
          "Knowledge Base"
        ],
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "summary": "Full extracted (indexed) text of a knowledge file",
        "description": "Every ingested chunk in document order — literally what `search_knowledge_base` searches. `text` is null when nothing is indexed yet.",
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Extracted text",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "text": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "text"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "text": "Todos los vehículos nuevos incluyen 3 años o 100.000 km de garantía de fábrica…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb-files/{id}/content": {
      "get": {
        "summary": "Download the raw file",
        "description": "The original bytes, with the stored `Content-Type` and an **inline** `Content-Disposition` so a browser previews text, markdown and PDFs rather than downloading them. This is not the `{ data }` envelope.\n\nA file whose row exists but whose stored object has gone answers a clean **404** naming that case, rather than a 500 — worth distinguishing in a UI, since it means the row should be deleted and the file re-uploaded.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The raw file bytes",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Replace a knowledge file with a new version",
        "description": "A **`multipart/form-data`** request with the new document in a field named `file`, like the upload. The file keeps its **id**: every agent it is attached to keeps seeing it, and every published version that references it still resolves — this is the path for \"the prices changed, here is the new PDF\", where delete + upload would have meant re-attaching the document to each agent by hand.\n\nThe name, content type and size become those of the new part. Its previously indexed chunks are purged **in the same transaction** as the swap, so the agent never quotes the superseded text while the new ingestion runs; the row answers with `status: ingesting` and settles to `ingested` (or `failed`) like an upload. Same 25 MB cap. A deleted or foreign file is a 404.\n\n**Optimistic concurrency.** Every replace bumps the file `version` (on the row, and in the `X-Config-Version` / `ETag` headers). Send the version you read as a form field `expected_version` (or `If-Match`): if someone uploaded a newer version since, the replace answers **409** `VERSION_CONFLICT` and nothing is stored. Omitting it still works (logged). Every replace leaves a `kb_files.replace` audit row naming the new and previous version, through REST and MCP alike.\n\n**Or a JSON body** (`application/json`): `{ filename, content, content_encoding?, content_type? }`, with `content` as UTF-8 text by default or base64 when `content_encoding` is `\"base64\"` (PDF, DOCX, images). The 25 MB cap applies to the decoded bytes; invalid base64 is a 400. The content type defaults from the filename extension. This is the shape an MCP connector sends through `call_operation`, which cannot build multipart. `expected_version` rides in the same JSON body.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The new version of the document, at most 25 MB"
                  },
                  "expected_version": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The file `version` you last read. Stale → 409 `VERSION_CONFLICT`, nothing stored. Optional (backward compatible); `If-Match` is accepted instead."
                  }
                }
              },
              "example": {
                "file": "(binary file part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension. The parser picks md/txt/csv/json/pdf/docx by it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The document. UTF-8 text by default; base64 when `content_encoding` is \"base64\". At most 25 MB once decoded."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for PDF / DOCX / images."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension: .md → text/markdown, .pdf → application/pdf, …"
                  },
                  "expected_version": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "The file `version` you last read. Stale → 409 `VERSION_CONFLICT`, nothing stored. Optional; `If-Match` is accepted instead."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ],
                "additionalProperties": false
              },
              "example": {
                "filename": "precios.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64",
                "expected_version": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The swapped file row, ingestion queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a6a6a6a6-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Garantías 2026.pdf",
                    "content_type": "application/pdf",
                    "size_bytes": 190112,
                    "status": "ingesting",
                    "agents": [
                      {
                        "id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "name": "Asistente de ventas"
                      }
                    ],
                    "created_at": "2026-09-01T13:00:00.000Z",
                    "updated_at": "2026-09-21T09:00:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb-files/{id}": {
      "delete": {
        "summary": "Delete a knowledge file (SOFT; keeps the stored object)",
        "description": "Removes the document from the workspace: it leaves the library, every agent attachment goes, and the embedded chunks are purged so retrieval can no longer return it. This is the \"wrong file\" action — to stop ONE agent using a file that others still need, detach it with `DELETE /ai-agents/{id}/knowledge/{fileId}`.\n\nADR 0093 — it is a **soft** delete: the `kb_file` row survives with `status: \"deleted\"` + `deleted_at`, and **the stored object is kept**. Every `ai_agent_version` references its files by id, so hard-deleting used to leave the version viewer unable to name what the agent knew and made the next publish of a draft restored from that version fail on the foreign key. Restoring such a version now un-deletes the file and re-ingests it from the kept object. `GET /kb-files/{id}/content` keeps serving a deleted file for exactly that viewer.\n\nAlways **204**, including for an id that does not exist or belongs to another workspace, so this endpoint cannot be used to probe for ids. It is not a confirmation that something was deleted.\n\nChunk removal is best-effort and logged on failure; the row is tombstoned either way.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/kb-files/{id}/reingest": {
      "post": {
        "summary": "Re-chunk and re-embed a file",
        "description": "Re-runs ingestion over the stored bytes — for a file whose ingestion failed, or to pick up a parser improvement. Returns the file row with its status reset; the actual work happens on the worker, so a 200 is an ACCEPTANCE and not a completed re-ingest.\n\nSafe to repeat: the worker deletes this file’s chunks before inserting the new ones, so re-ingesting never doubles a document up in retrieval. Nothing is re-uploaded and the id does not change.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The file row, queued for re-ingestion",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a6a6a6a6-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Garantías 2026.pdf",
                    "content_type": "application/pdf",
                    "size_bytes": 182304,
                    "status": "ingesting",
                    "agents": [
                      {
                        "id": "a3a3a3a3-0000-4000-8000-000000000001",
                        "name": "Asistente de ventas"
                      }
                    ],
                    "created_at": "2026-09-01T13:00:00.000Z",
                    "updated_at": "2026-09-01T13:05:00.000Z",
                    "deleted_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb-files/generate-from-url": {
      "post": {
        "summary": "Draft a document from a URL (synchronous)",
        "description": "Fetches the page — or, with `crawl: true`, up to `max_pages` (max 25, default 1) — and has an LLM consolidate them into one markdown document. Answers `{ title, markdown, source_url, page_count }`.\n\nThe document is **returned, not stored** — no `kb_file` row is created and nothing becomes searchable. This step exists so a human can read and edit what the crawler made of the site before it becomes something the agent will quote to a customer. Save it with the multipart `POST /kb-files`.\n\n**This blocks for as long as the crawl and the LLM take**, which for a multi-page site is tens of seconds. Use `/crawl/start` instead when you want progress to show, and keep this one for the single-page case.\n\nA bare domain is accepted and normalised. A URL with no readable content is a 400 — a page that is entirely JavaScript often lands here.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 1
                  },
                  "crawl": {
                    "type": "boolean"
                  },
                  "max_pages": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 25
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://www.midealer.cl/garantia",
                "crawl": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The generated markdown draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "title": "Garantía — Mi Dealer",
                    "markdown": "# Garantía\n\nTodos los vehículos nuevos incluyen 3 años o 100.000 km de garantía de fábrica.",
                    "source_url": "https://www.midealer.cl/garantia",
                    "page_count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/kb-files/crawl/start": {
      "post": {
        "summary": "Start an asynchronous site crawl",
        "description": "Kicks off a crawl of up to `max_pages` (max 25) and returns `{ job_id }` immediately. Poll `GET /kb-files/crawl/{jobId}` for progress, then call `POST /kb-files/crawl/{jobId}/generate` once it is done.\n\nKeep the `job_id`: it is the ONLY handle on the crawl and there is no endpoint that lists your jobs. Losing it means starting again.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "minLength": 1
                  },
                  "max_pages": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 25
                  }
                },
                "required": [
                  "url"
                ]
              },
              "example": {
                "url": "https://www.midealer.cl",
                "max_pages": 10
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ job_id }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "job_id": "b3b3b3b3-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/kb-files/crawl/{jobId}": {
      "get": {
        "summary": "Crawl progress",
        "description": "`{ status, done, pages_found, pages_succeeded, pages_failed, error, pages }`, where `pages` lists each URL with its title and HTTP status as it is discovered — enough to show live progress.\n\n**Poll `done`, not `status`.** `done` is true for every terminal state, failures included, so a crawl that errored still reports `done: true` with `error` set. Treating a non-`completed` status as \"keep waiting\" polls forever.\n\n**The `jobId` is the only credential on this route.** Crawl jobs live in the crawler service, which has no tenant column, so this endpoint checks that the caller holds a valid job id and not that the job belongs to their workspace. The id is a random uuid handed only to whoever started the crawl — treat it like a bearer token and keep it out of logs and shared URLs.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "jobId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Crawl progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "status": "completed",
                    "done": true,
                    "pages_found": 6,
                    "pages_succeeded": 6,
                    "pages_failed": 0,
                    "error": null,
                    "pages": [
                      {
                        "url": "https://www.midealer.cl",
                        "title": "Inicio",
                        "http_status": 200
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/kb-files/crawl/{jobId}/generate": {
      "post": {
        "summary": "Turn a finished crawl into a document",
        "description": "Consolidates the crawled pages into one markdown document, in the same shape `/generate-from-url` returns.\n\nThe document is **returned, not stored** — no `kb_file` row is created and nothing becomes searchable. This step exists so a human can read and edit what the crawler made of the site before it becomes something the agent will quote to a customer. Save it with the multipart `POST /kb-files`.\n\nUsually instant: when the crawler’s completion webhook is configured, the document is generated as the crawl finishes and cached, and this call just serves it. Without that it generates live and takes as long as the LLM does. Either way the result is the same, so callers should not depend on the timing.\n\nCall it once the crawl reports `done`. Calling it early consolidates only the pages fetched so far, which succeeds and quietly produces a partial document rather than failing.\n\n**The `jobId` is the only credential on this route.** Crawl jobs live in the crawler service, which has no tenant column, so this endpoint checks that the caller holds a valid job id and not that the job belongs to their workspace. The id is a random uuid handed only to whoever started the crawl — treat it like a bearer token and keep it out of logs and shared URLs.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Knowledge Files"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "jobId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The generated markdown draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "title": "Garantía — Mi Dealer",
                    "markdown": "# Garantía\n\nTodos los vehículos nuevos incluyen 3 años o 100.000 km de garantía de fábrica.",
                    "source_url": "https://www.midealer.cl/garantia",
                    "page_count": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tool-credentials": {
      "get": {
        "summary": "List the workspace’s tool credentials",
        "description": "Every credential with its `value_preview` (never the plaintext), `kind`, `metadata`, `last_used_at` and, while a rotation is in its grace window, `rotation_grace_until`. Unpaginated, with a `total`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tool Credentials"
        ],
        "responses": {
          "200": {
            "description": "Tool credentials",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "a7a7a7a7-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "weather_api_key",
                      "kind": "api_key",
                      "value_preview": "••••wx91",
                      "metadata": {},
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-09-12T10:00:00.000Z",
                      "updated_at": "2026-09-12T10:00:00.000Z",
                      "last_used_at": "2026-09-21T18:04:11.000Z",
                      "rotation_grace_until": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a tool credential",
        "description": "`name` must start with a letter and hold only letters, digits, `_` or `-`; it is unique per workspace and a collision is a **409**. `value` is the plaintext secret — encrypted immediately and never returned again; every later read answers `value_preview` instead. Reference it from a custom tool’s `request_template` as `${secret.NAME}` and set `auth_config.credential_id` to this row’s `id`. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tool Credentials"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 64,
                    "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$"
                  },
                  "kind": {
                    "type": "string",
                    "enum": [
                      "api_key",
                      "bearer",
                      "basic",
                      "oauth2_client",
                      "static_header"
                    ]
                  },
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16384
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  }
                },
                "required": [
                  "name",
                  "kind",
                  "value"
                ]
              },
              "example": {
                "name": "weather_api_key",
                "kind": "api_key",
                "value": "wapi_live_abc123wx91"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "weather_api_key",
                    "kind": "api_key",
                    "value_preview": "••••wx91",
                    "metadata": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-12T10:00:00.000Z",
                    "updated_at": "2026-09-12T10:00:00.000Z",
                    "last_used_at": "2026-09-21T18:04:11.000Z",
                    "rotation_grace_until": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A credential with that name already exists"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/tool-credentials/{id}": {
      "get": {
        "summary": "Fetch one tool credential",
        "description": "Same shape as the list entry — `value_preview`, never the plaintext.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tool Credentials"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "weather_api_key",
                    "kind": "api_key",
                    "value_preview": "••••wx91",
                    "metadata": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-12T10:00:00.000Z",
                    "updated_at": "2026-09-12T10:00:00.000Z",
                    "last_used_at": "2026-09-21T18:04:11.000Z",
                    "rotation_grace_until": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a tool credential",
        "description": "Removes the credential. Any custom tool whose `auth_config` still points at it starts failing its next call with an auth error — update or disable that tool first. Past `tool_invocation` audit rows survive unaffected (they carry a redacted snapshot, never the credential itself). 204.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tool Credentials"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update or rotate a tool credential",
        "description": "Every field optional; at least one is required (an empty body is a 400). Sending `value` ROTATES the secret: the new value is encrypted, `value_preview` updates, and the OLD value stays decryptable for a 24-hour grace window (`rotation_grace_until`) so a tool call already in flight with the old secret does not fail mid-request. `name` and `metadata` can be sent alongside `value` in the same call, or on their own to rename/annotate without rotating.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tool Credentials"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 64,
                    "pattern": "^[a-zA-Z][a-zA-Z0-9_-]{0,63}$"
                  },
                  "metadata": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "value": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 16384
                  }
                }
              },
              "example": {
                "value": "wapi_live_def456zz44"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated credential",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "a7a7a7a7-0000-4000-8000-000000000002",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "weather_api_key",
                    "kind": "api_key",
                    "value_preview": "••••zz44",
                    "metadata": {},
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-09-12T10:00:00.000Z",
                    "updated_at": "2026-09-22T09:00:00.000Z",
                    "last_used_at": "2026-09-21T18:04:11.000Z",
                    "rotation_grace_until": "2026-09-23T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/macros": {
      "get": {
        "summary": "List macros",
        "description": "Every macro in the workspace with its content, actions, params and usage counters. Unfiltered — `active`, `visibility` and `available_for` come back on each row for the caller to filter on, and inactive macros are included.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "responses": {
          "200": {
            "description": "Macros",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Cierre agradecido",
                      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                      "category": "cierre",
                      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                      "shortcut": "cierre",
                      "actions": [
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000001",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000002",
                          "type": "change_status",
                          "value": "resolved"
                        }
                      ],
                      "params": [
                        {
                          "key": "nombre",
                          "label": "Nombre del cliente",
                          "input_type": "text"
                        }
                      ],
                      "icon": "play",
                      "visibility": "public",
                      "available_for": "everyone",
                      "run_when": [
                        "manual"
                      ],
                      "active": true,
                      "usage_count": 42,
                      "last_used_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a macro",
        "description": "Only `name` is required. An **action-only macro is valid** — one that just changes status and adds a label, with no `content`, is a normal thing to build.\n\n`actions` is an untyped list: the step objects are stored as given and validated only when the macro RUNS. A typo in a step `type` creates cleanly and then reports `unknown action type` on every execution, so run a macro once after authoring it.\n\nEach entry in `params` declares one `[[manual]]` placeholder, and its `key` must match the placeholder text in `content` for the composer to pair them up. Defaults: `icon: \"play\"`, `visibility: \"public\"`, `available_for: \"everyone\"`, `run_when: [\"manual\"]`, `active: true`. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 8000,
                    "default": ""
                  },
                  "shortcut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50
                  },
                  "actions": {
                    "type": "array",
                    "items": {}
                  },
                  "params": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "label": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "input_type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "currency",
                            "date",
                            "select"
                          ]
                        },
                        "placeholder": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "key"
                      ]
                    }
                  },
                  "icon": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "private"
                    ]
                  },
                  "available_for": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "run_when": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 40
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Cierre agradecido",
                "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                "actions": [
                  {
                    "id": "ac7a0000-0000-4000-8000-000000000001",
                    "type": "add_tag",
                    "value": [
                      "resuelto"
                    ]
                  }
                ],
                "params": [
                  {
                    "key": "nombre",
                    "label": "Nombre del cliente",
                    "input_type": "text"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created macro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Cierre agradecido",
                    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                    "category": "cierre",
                    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                    "shortcut": "cierre",
                    "actions": [
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000001",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000002",
                        "type": "change_status",
                        "value": "resolved"
                      }
                    ],
                    "params": [
                      {
                        "key": "nombre",
                        "label": "Nombre del cliente",
                        "input_type": "text"
                      }
                    ],
                    "icon": "play",
                    "visibility": "public",
                    "available_for": "everyone",
                    "run_when": [
                      "manual"
                    ],
                    "active": true,
                    "usage_count": 42,
                    "last_used_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/macros/{id}": {
      "get": {
        "summary": "Fetch one macro",
        "description": "The macro as stored, including `usage_count` and `last_used_at`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The macro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Cierre agradecido",
                    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                    "category": "cierre",
                    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                    "shortcut": "cierre",
                    "actions": [
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000001",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000002",
                        "type": "change_status",
                        "value": "resolved"
                      }
                    ],
                    "params": [
                      {
                        "key": "nombre",
                        "label": "Nombre del cliente",
                        "input_type": "text"
                      }
                    ],
                    "icon": "play",
                    "visibility": "public",
                    "available_for": "everyone",
                    "run_when": [
                      "manual"
                    ],
                    "active": true,
                    "usage_count": 42,
                    "last_used_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a macro",
        "description": "A PUT that behaves as a PATCH; at least one field is required. `actions` and `params` are each replaced wholesale when sent, so a step list must be sent complete.\n\nEditing takes effect immediately for anyone about to run it. The usage counters are not editable here.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 100
                  },
                  "content": {
                    "type": "string",
                    "maxLength": 8000,
                    "default": ""
                  },
                  "shortcut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 50
                  },
                  "actions": {
                    "type": "array",
                    "items": {}
                  },
                  "params": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "key": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        },
                        "label": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "input_type": {
                          "type": "string",
                          "enum": [
                            "text",
                            "currency",
                            "date",
                            "select"
                          ]
                        },
                        "placeholder": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      },
                      "required": [
                        "key"
                      ]
                    }
                  },
                  "icon": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "visibility": {
                    "type": "string",
                    "enum": [
                      "public",
                      "private"
                    ]
                  },
                  "available_for": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "run_when": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 40
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated macro",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d3d3d3d3-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Cierre agradecido",
                    "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                    "category": "cierre",
                    "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                    "shortcut": "cierre",
                    "actions": [
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000001",
                        "type": "add_tag",
                        "value": [
                          "resuelto"
                        ]
                      },
                      {
                        "id": "ac7a0000-0000-4000-8000-000000000002",
                        "type": "change_status",
                        "value": "resolved"
                      }
                    ],
                    "params": [
                      {
                        "key": "nombre",
                        "label": "Nombre del cliente",
                        "input_type": "text"
                      }
                    ],
                    "icon": "play",
                    "visibility": "public",
                    "available_for": "everyone",
                    "run_when": [
                      "manual"
                    ],
                    "active": false,
                    "usage_count": 42,
                    "last_used_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a macro",
        "description": "Removes the macro. Everything it already did — messages sent, labels applied, status changes — stays; a macro is an action, not a relationship. Set `active: false` to retire one without losing its usage history.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/macros/{id}/apply": {
      "post": {
        "summary": "Run a macro as-is",
        "description": "Executes the macro against a conversation with **no chance to review the text first** — templates are rendered and the reply is sent straight to the customer. For the reviewed flow use `GET /macros/{id}/prepare` then `POST /macros/{id}/run`; this endpoint is for macros with no manual params, and for automation.\n\nWith an empty body it does nothing but bump `usage_count` and return an empty step log — the legacy \"the agent clicked it\" call. Execution only begins once `conversation_id` is supplied.\n\nAnswers `{ macro, executed }`, where `executed` is one `{ type, ok, error }` entry per step in order. **A 200 does not mean the macro worked** — a failed send and a failed label both appear as `ok: false` inside a successful response, and the steps after them still ran. Surface the failures; they are not retried.\n\n`send_attachment` sends a file of the library (`GET /media-assets`): its `value` is the file id (`asset_name` is display-only). It runs in step order, through the same checks as `POST /conversations/{id}/media-assets/{assetId}/send`; a refusal reports its code as `error` (e.g. `MEDIA_ASSET_NOT_SENDABLE`), a value that is not a file id reports `Elige un archivo de la Biblioteca`, and a send parked by the política de envíos reports `status: pending_approval`. `mute_conversation` is accepted by the builder but not implemented and reports `not yet supported`. An unrecognised type reports `unknown action type`.\n\n`run_webhook` is a FLAG rather than a step: its presence anywhere in `actions` makes the whole run fire one `macro.applied` webhook after the loop, with the full step log. Listing it twice still fires once, and its position in the list has no effect.\n\nTicket-bound steps act on `ticket_id` when given, otherwise on the conversation’s most recent ticket. A macro with label or stage steps run against a conversation that has no ticket records those steps as failed while the reply still goes out.\n\n`usage_count` is bumped BEFORE the steps run, so it counts attempts rather than successes.\n\nAttribution follows the credential: a signed-in teammate’s reply is stored as a human agent message, while an API key with no user behind it is stored as an AI-agent message. That also decides the web kill-switch — only a human-initiated run bypasses it.\n\n**Scopes:** `macros:read`, `conversations:write` and `messages:send`, all three. A reply sent by an API key, a personal token or a connected app goes through the política de envíos like any API send: a Bloqueo or an unacknowledged Advertencia fails the `send_message` step, and a connected app past its fan-out allowance gets the reply parked for a member's approval — the step is then `ok` with `status: \"pending_approval\"` and an `approval_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "ticket_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              },
              "example": {
                "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ macro, executed }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "macro": {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Cierre agradecido",
                      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                      "category": "cierre",
                      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                      "shortcut": "cierre",
                      "actions": [
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000001",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000002",
                          "type": "change_status",
                          "value": "resolved"
                        }
                      ],
                      "params": [
                        {
                          "key": "nombre",
                          "label": "Nombre del cliente",
                          "input_type": "text"
                        }
                      ],
                      "icon": "play",
                      "visibility": "public",
                      "available_for": "everyone",
                      "run_when": [
                        "manual"
                      ],
                      "active": true,
                      "usage_count": 42,
                      "last_used_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    },
                    "executed": [
                      {
                        "type": "add_tag",
                        "ok": true,
                        "error": null
                      },
                      {
                        "type": "change_status",
                        "ok": true,
                        "error": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/macros/{id}/prepare": {
      "get": {
        "summary": "Resolve a macro for the composer",
        "description": "Renders the macro against a live conversation WITHOUT sending or changing anything. Answers `{ macro, segments, manual_params, effects }`.\n\n`segments` is the reply broken into resolved text and still-unfilled placeholders, so a composer can render the draft with gaps in place. `manual_params` is the `[[manual]]` params the agent must fill, with their input types and options. `effects` summarises the non-message actions so the agent can see what else pressing send will do.\n\nThe reply text is the macro’s FIRST `send_message` action, falling back to `content`. A macro with several `send_message` steps previews only the first here, though running it sends them all.\n\nRead-only and side-effect free — unlike `/apply`, it does not touch `usage_count`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "conversation_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ macro, segments, manual_params, effects }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "macro": {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Cierre agradecido",
                      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                      "category": "cierre",
                      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                      "shortcut": "cierre",
                      "actions": [
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000001",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000002",
                          "type": "change_status",
                          "value": "resolved"
                        }
                      ],
                      "params": [
                        {
                          "key": "nombre",
                          "label": "Nombre del cliente",
                          "input_type": "text"
                        }
                      ],
                      "icon": "play",
                      "visibility": "public",
                      "available_for": "everyone",
                      "run_when": [
                        "manual"
                      ],
                      "active": true,
                      "usage_count": 42,
                      "last_used_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    },
                    "segments": [
                      {
                        "text": "Gracias por escribirnos, ",
                        "resolved": true
                      },
                      {
                        "text": "[[nombre]]",
                        "resolved": false
                      }
                    ],
                    "manual_params": [
                      {
                        "key": "nombre",
                        "label": "Nombre del cliente",
                        "input_type": "text"
                      }
                    ],
                    "effects": [
                      "Etiqueta: resuelto",
                      "Cambia el estado a resolved"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/macros/{id}/run": {
      "post": {
        "summary": "Send the reviewed draft and run the side-effects",
        "description": "The second half of the composer flow: takes the text the agent actually approved and executes the macro’s side-effects in one call.\n\n**`draft_text` supersedes the macro’s `send_message` steps.** When it is present that text is sent and every `send_message` step is skipped, so the customer never receives both. Send it already substituted — `param_values` are applied to the macro’s own templates (notes, labels), not to your draft.\n\nOmit `draft_text` and the macro’s own `send_message` steps run normally, rendered with `param_values`.\n\nAnswers `{ macro, executed }`, where `executed` is one `{ type, ok, error }` entry per step in order. **A 200 does not mean the macro worked** — a failed send and a failed label both appear as `ok: false` inside a successful response, and the steps after them still ran. Surface the failures; they are not retried.\n\n`send_attachment` sends a file of the library (`GET /media-assets`): its `value` is the file id (`asset_name` is display-only). It runs in step order, through the same checks as `POST /conversations/{id}/media-assets/{assetId}/send`; a refusal reports its code as `error` (e.g. `MEDIA_ASSET_NOT_SENDABLE`), a value that is not a file id reports `Elige un archivo de la Biblioteca`, and a send parked by the política de envíos reports `status: pending_approval`. `mute_conversation` is accepted by the builder but not implemented and reports `not yet supported`. An unrecognised type reports `unknown action type`.\n\n`run_webhook` is a FLAG rather than a step: its presence anywhere in `actions` makes the whole run fire one `macro.applied` webhook after the loop, with the full step log. Listing it twice still fires once, and its position in the list has no effect.\n\nTicket-bound steps act on `ticket_id` when given, otherwise on the conversation’s most recent ticket. A macro with label or stage steps run against a conversation that has no ticket records those steps as failed while the reply still goes out.\n\nDelivery is attempted BEFORE the message is persisted, so a channel failure shows up as a failed step rather than as a message in the thread that never left.\n\n**Scopes:** `macros:read`, `conversations:write` and `messages:send`, all three. A reply sent by an API key, a personal token or a connected app goes through the política de envíos like any API send: a Bloqueo or an unacknowledged Advertencia fails the `send_message` step, and a connected app past its fan-out allowance gets the reply parked for a member's approval — the step is then `ok` with `status: \"pending_approval\"` and an `approval_id`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Macros"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "ticket_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "draft_text": {
                    "type": "string",
                    "maxLength": 8000
                  },
                  "param_values": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "string"
                    }
                  }
                },
                "required": [
                  "conversation_id"
                ]
              },
              "example": {
                "conversation_id": "bbbbbbbb-0000-4000-8000-000000000001",
                "draft_text": "Gracias por escribirnos, María. Cualquier otra duda, aquí estamos.",
                "param_values": {
                  "nombre": "María"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ macro, executed }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "macro": {
                      "id": "d3d3d3d3-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Cierre agradecido",
                      "description": "Agradece, etiqueta como resuelto y cambia el estado del ticket.",
                      "category": "cierre",
                      "content": "Gracias por escribirnos, [[nombre]]. Cualquier otra duda, aquí estamos.",
                      "shortcut": "cierre",
                      "actions": [
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000001",
                          "type": "add_tag",
                          "value": [
                            "resuelto"
                          ]
                        },
                        {
                          "id": "ac7a0000-0000-4000-8000-000000000002",
                          "type": "change_status",
                          "value": "resolved"
                        }
                      ],
                      "params": [
                        {
                          "key": "nombre",
                          "label": "Nombre del cliente",
                          "input_type": "text"
                        }
                      ],
                      "icon": "play",
                      "visibility": "public",
                      "available_for": "everyone",
                      "run_when": [
                        "manual"
                      ],
                      "active": true,
                      "usage_count": 42,
                      "last_used_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    },
                    "executed": [
                      {
                        "type": "add_tag",
                        "ok": true,
                        "error": null
                      },
                      {
                        "type": "change_status",
                        "ok": true,
                        "error": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/inbox-views": {
      "get": {
        "summary": "List the inbox views you see, with their counts",
        "description": "The workspace views (position order), then your personal views. A view: `{ id, name, scope: workspace|personal, owner_user_id, filters, exclude_from_waiting, position, created_by, created_at, updated_at, editable }` — `editable` says whether the caller may change it.\n\nEach view carries `count` — exactly the total of `GET /conversations?viewId=<id>` for you (your row visibility applies) — and `waiting_count`, how many of its matches have a customer waiting on a human (the sidebar badge of a view with `exclude_from_waiting`). `counts=false` skips both.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "counts",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Inbox views",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                      "name": "Piden 18 Pro Max",
                      "scope": "workspace",
                      "owner_user_id": null,
                      "filters": {
                        "active": true,
                        "attributes": [
                          {
                            "key": "*",
                            "op": "contains",
                            "value": "18 pro max"
                          }
                        ]
                      },
                      "exclude_from_waiting": true,
                      "position": 0,
                      "created_by": "0b9e2f4a-8c1d-4f37-b6a2-5d3e7c9a1b20",
                      "created_at": "2026-11-04T12:00:00.000Z",
                      "updated_at": "2026-11-04T12:00:00.000Z",
                      "editable": true,
                      "count": 21,
                      "waiting_count": 15
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an inbox view",
        "description": "`scope: personal` (the default) is yours alone; `scope: workspace` is the whole team’s and needs `routing:write` (a workspace admin). `filters` takes the `InboxViewFilters` subset of `GET /conversations` filters and needs at least one criterion besides `waiting`. An attribute condition `{ key, op, value }` compares the value case- and accent-insensitively; `key: \"*\"` matches it under any attribute key (one view for `modelo_buscado` and `handoff.reason` alike). `exclude_from_waiting: true` («Sacar de Esperando respuesta») is refused (400) on a personal view; on a workspace view it never takes out an urgent (`handoff_urgent`) thread. A name already used in the same scope (case-insensitive) is a 409. At most 50 views per scope. Answers 201 with the view.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "scope": {
                    "type": "string",
                    "enum": [
                      "workspace",
                      "personal"
                    ],
                    "default": "personal"
                  },
                  "filters": {
                    "$ref": "#/components/schemas/InboxViewFilters"
                  },
                  "exclude_from_waiting": {
                    "type": "boolean",
                    "default": false
                  }
                },
                "required": [
                  "name",
                  "filters"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Piden 18 Pro Max",
                "scope": "workspace",
                "filters": {
                  "active": true,
                  "attributes": [
                    {
                      "key": "*",
                      "op": "contains",
                      "value": "18 pro max"
                    }
                  ]
                },
                "exclude_from_waiting": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                    "name": "Piden 18 Pro Max",
                    "scope": "workspace",
                    "owner_user_id": null,
                    "filters": {
                      "active": true,
                      "attributes": [
                        {
                          "key": "*",
                          "op": "contains",
                          "value": "18 pro max"
                        }
                      ]
                    },
                    "exclude_from_waiting": true,
                    "position": 0,
                    "created_by": "0b9e2f4a-8c1d-4f37-b6a2-5d3e7c9a1b20",
                    "created_at": "2026-11-04T12:00:00.000Z",
                    "updated_at": "2026-11-04T12:00:00.000Z",
                    "editable": true,
                    "count": 21,
                    "waiting_count": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/inbox-views/attribute-keys": {
      "get": {
        "summary": "Conversation-attribute keys and their common values",
        "description": "The workspace’s conversation-attribute keys (what the AI agent recorded with `add_metadata`, or a person set), most used first, each with `count` (conversations carrying it) and `top_values` — the `top` most frequent scalar values, cut to 120 characters — for a view editor’s picker. `key` narrows to one key.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "key",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 50,
              "default": 5
            },
            "required": false,
            "name": "top",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Attribute keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "key": "modelo",
                      "count": 1334,
                      "top_values": [
                        {
                          "value": "iPhone 18 Pro 256 GB",
                          "count": 72
                        },
                        {
                          "value": "iPhone 18 Pro Max",
                          "count": 26
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/inbox-views/reorder": {
      "post": {
        "summary": "Reorder inbox views",
        "description": "Sets each listed view’s `position` to its index in `ids`. All ids must be views of `scope` that you write (workspace: `routing:write`; personal: your own); views left out keep their position. Answers the views you see, in their new order.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "workspace",
                      "personal"
                    ]
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  }
                },
                "required": [
                  "scope",
                  "ids"
                ],
                "additionalProperties": false
              },
              "example": {
                "scope": "workspace",
                "ids": [
                  "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                  "9d4b7a10-52c3-4e8f-a1b6-7e0c3f2d8a55"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Inbox views, reordered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                      "name": "Piden 18 Pro Max",
                      "scope": "workspace",
                      "owner_user_id": null,
                      "filters": {
                        "active": true,
                        "attributes": [
                          {
                            "key": "*",
                            "op": "contains",
                            "value": "18 pro max"
                          }
                        ]
                      },
                      "exclude_from_waiting": true,
                      "position": 0,
                      "created_by": "0b9e2f4a-8c1d-4f37-b6a2-5d3e7c9a1b20",
                      "created_at": "2026-11-04T12:00:00.000Z",
                      "updated_at": "2026-11-04T12:00:00.000Z",
                      "editable": true,
                      "count": 21,
                      "waiting_count": 15
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/inbox-views/{id}": {
      "get": {
        "summary": "Fetch one inbox view",
        "description": "A view: `{ id, name, scope: workspace|personal, owner_user_id, filters, exclude_from_waiting, position, created_by, created_at, updated_at, editable }` — `editable` says whether the caller may change it. Another member’s personal view is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                    "name": "Piden 18 Pro Max",
                    "scope": "workspace",
                    "owner_user_id": null,
                    "filters": {
                      "active": true,
                      "attributes": [
                        {
                          "key": "*",
                          "op": "contains",
                          "value": "18 pro max"
                        }
                      ]
                    },
                    "exclude_from_waiting": true,
                    "position": 0,
                    "created_by": "0b9e2f4a-8c1d-4f37-b6a2-5d3e7c9a1b20",
                    "created_at": "2026-11-04T12:00:00.000Z",
                    "updated_at": "2026-11-04T12:00:00.000Z",
                    "editable": true,
                    "count": 21,
                    "waiting_count": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an inbox view",
        "description": "Deletes the view (never a conversation). A view with `exclude_from_waiting` puts its matches back in «Esperando respuesta». Answers 204.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update an inbox view",
        "description": "Any of `name`, `filters` (replaced whole), `exclude_from_waiting`, `position`. Same rules as create; the scope cannot change. Turning `exclude_from_waiting` off puts the matches back in «Esperando respuesta» on the next read.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Inbox views"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "filters": {
                    "$ref": "#/components/schemas/InboxViewFilters"
                  },
                  "exclude_from_waiting": {
                    "type": "boolean"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "name": "Piden 18 Pro Max (sin respuesta)",
                "exclude_from_waiting": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6a1f0c7e-3b8d-4e52-9a51-0c2d7b1e9f44",
                    "name": "Piden 18 Pro Max",
                    "scope": "workspace",
                    "owner_user_id": null,
                    "filters": {
                      "active": true,
                      "attributes": [
                        {
                          "key": "*",
                          "op": "contains",
                          "value": "18 pro max"
                        }
                      ]
                    },
                    "exclude_from_waiting": true,
                    "position": 0,
                    "created_by": "0b9e2f4a-8c1d-4f37-b6a2-5d3e7c9a1b20",
                    "created_at": "2026-11-04T12:00:00.000Z",
                    "updated_at": "2026-11-04T12:00:00.000Z",
                    "editable": true,
                    "count": 21,
                    "waiting_count": 15
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/voice-clips": {
      "get": {
        "summary": "List voice clips",
        "description": "**Deprecated** — use `/media-assets` (voice clips are its audio files, same ids).\n\nEvery clip in the workspace library, newest first, expired ones included and marked `expired: true`.\n\nWith `live=true` the answer is only the clips that may be SENT now: not expired, and whose model filter is empty or names `model` (case- and whitespace-insensitive). Without `model`, only clips with no model filter qualify. `tag` (comma-separated) keeps clips carrying any of the tags.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`true` = only the clips that may be sent now: not expired and admitted by `model`."
            },
            "required": false,
            "description": "`true` = only the clips that may be sent now: not expired and admitted by `model`.",
            "name": "live",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120,
              "description": "With `live=true`: the model under discussion. Without it only clips with no model filter qualify."
            },
            "required": false,
            "description": "With `live=true`: the model under discussion. Without it only clips with no model filter qualify.",
            "name": "model",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "With `live=true`: comma-separated tags; a clip qualifies when it carries any."
            },
            "required": false,
            "description": "With `live=true`: comma-separated tags; a clip qualifies when it carries any.",
            "name": "tag",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Voice clips",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "id": "6b0f3c1e-2a4d-4f7e-9b1a-0c2d3e4f5a6b",
                    "name": "Garantía 6 meses",
                    "tags": [
                      "garantia",
                      "cierre"
                    ],
                    "when_to_use": "Cuando el cliente pregunta por la garantía de un seminuevo.",
                    "expires_at": null,
                    "expired": false,
                    "models": [
                      "iPhone 13",
                      "iPhone 13 Pro",
                      "iPhone 14"
                    ],
                    "transcript": "Hola, te cuento que todos nuestros equipos tienen seis meses de garantía.",
                    "mime_type": "audio/ogg",
                    "byte_size": 48213,
                    "duration_ms": 12480,
                    "original_filename": "garantia.ogg",
                    "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                    "created_at": "2026-10-28T15:04:05.000Z",
                    "updated_at": "2026-10-28T15:04:05.000Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a voice clip",
        "description": "**Deprecated** — use `/media-assets` (voice clips are its audio files, same ids).\n\nUploads the audio (`file`: OGG/Opus, MP3, M4A/AAC, WAV, WebM or AMR, at most 16 MB; 415 for another type, 413 above the cap) with its details. The clip is transcribed on upload unless `transcript` is sent; when speech-to-text fails or hears nothing usable, `transcript` is null and can be filled with `PATCH`. `duration_ms` is measured from the audio. `name` defaults to the file name. Answers 201 with the clip.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Defaults to the file name without its extension."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the clip (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "«Cuándo usarlo»: when this clip fits, for the team and the agent."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the clip applies to, spelled as in the catalog (`product.model`, e.g. \"iPhone 13 Pro\"). Empty = any model. Matched case- and whitespace-insensitively."
                  },
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "What the clip says. When given, speech-to-text is skipped."
                  },
                  "file": {}
                }
              },
              "example": {
                "file": "(binary audio part)",
                "name": "Garantía 6 meses",
                "tags": "garantia,cierre",
                "when_to_use": "Cuando el cliente pregunta por la garantía."
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Defaults to the file name without its extension."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the clip (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "«Cuándo usarlo»: when this clip fits, for the team and the agent."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the clip applies to, spelled as in the catalog (`product.model`, e.g. \"iPhone 13 Pro\"). Empty = any model. Matched case- and whitespace-insensitively."
                  },
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "What the clip says. When given, speech-to-text is skipped."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "garantia.ogg",
                "content": "T2dnUwACAAAAAAAAAAA…",
                "content_encoding": "base64",
                "name": "Garantía 6 meses",
                "tags": [
                  "garantia",
                  "cierre"
                ],
                "when_to_use": "Cuando el cliente pregunta por la garantía.",
                "models": [
                  "iPhone 13",
                  "iPhone 14"
                ],
                "expires_at": "2026-12-31T23:59:59-03:00"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created clip",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "id": "6b0f3c1e-2a4d-4f7e-9b1a-0c2d3e4f5a6b",
                  "name": "Garantía 6 meses",
                  "tags": [
                    "garantia",
                    "cierre"
                  ],
                  "when_to_use": "Cuando el cliente pregunta por la garantía de un seminuevo.",
                  "expires_at": null,
                  "expired": false,
                  "models": [
                    "iPhone 13",
                    "iPhone 13 Pro",
                    "iPhone 14"
                  ],
                  "transcript": "Hola, te cuento que todos nuestros equipos tienen seis meses de garantía.",
                  "mime_type": "audio/ogg",
                  "byte_size": 48213,
                  "duration_ms": 12480,
                  "original_filename": "garantia.ogg",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-28T15:04:05.000Z",
                  "updated_at": "2026-10-28T15:04:05.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/voice-clips/{id}": {
      "get": {
        "summary": "Fetch one voice clip",
        "description": "**Deprecated** — use `GET /media-assets/{id}`.\n\nThe clip with its details, transcript and `expired` state.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The clip",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "6b0f3c1e-2a4d-4f7e-9b1a-0c2d3e4f5a6b",
                  "name": "Garantía 6 meses",
                  "tags": [
                    "garantia",
                    "cierre"
                  ],
                  "when_to_use": "Cuando el cliente pregunta por la garantía de un seminuevo.",
                  "expires_at": null,
                  "expired": false,
                  "models": [
                    "iPhone 13",
                    "iPhone 13 Pro",
                    "iPhone 14"
                  ],
                  "transcript": "Hola, te cuento que todos nuestros equipos tienen seis meses de garantía.",
                  "mime_type": "audio/ogg",
                  "byte_size": 48213,
                  "duration_ms": 12480,
                  "original_filename": "garantia.ogg",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-28T15:04:05.000Z",
                  "updated_at": "2026-10-28T15:04:05.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a voice clip",
        "description": "**Deprecated** — use `/media-assets` (voice clips are its audio files, same ids).\n\nRemoves the clip and its audio from the library. Voice notes already sent in conversations stay there. To stop offering a clip without losing it, set `expires_at` instead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a voice clip",
        "description": "**Deprecated** — use `/media-assets` (voice clips are its audio files, same ids).\n\nChanges the details and the transcript; the audio itself is replaced by uploading a new clip. `tags` and `models` are replaced wholesale. `expires_at: null` removes the expiry (an expired clip becomes sendable again). At least one field is required.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the clip (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the clip stops being offered for sending (ISO 8601). Null = never. An expired clip stays in the library, marked `expired`."
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the clip applies to, spelled as in the catalog (`product.model`, e.g. \"iPhone 13 Pro\"). Empty = any model. Matched case- and whitespace-insensitively."
                  },
                  "transcript": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 8000,
                    "description": "The corrected transcript. Empty or null clears it."
                  }
                }
              },
              "example": {
                "transcript": "Hola, te cuento que todos nuestros equipos tienen seis meses de garantía.",
                "expires_at": "2026-11-30T23:59:59-03:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated clip",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "6b0f3c1e-2a4d-4f7e-9b1a-0c2d3e4f5a6b",
                  "name": "Garantía 6 meses",
                  "tags": [
                    "garantia",
                    "cierre"
                  ],
                  "when_to_use": "Cuando el cliente pregunta por la garantía de un seminuevo.",
                  "expires_at": null,
                  "expired": false,
                  "models": [
                    "iPhone 13",
                    "iPhone 13 Pro",
                    "iPhone 14"
                  ],
                  "transcript": "Hola, te cuento que todos nuestros equipos tienen seis meses de garantía.",
                  "mime_type": "audio/ogg",
                  "byte_size": 48213,
                  "duration_ms": 12480,
                  "original_filename": "garantia.ogg",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-28T15:04:05.000Z",
                  "updated_at": "2026-10-28T15:04:05.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/voice-clips/{id}/play-url": {
      "get": {
        "summary": "Get a play URL for a voice clip",
        "description": "**Deprecated** — use `/media-assets` (voice clips are its audio files, same ids).\n\nA short-lived signed URL (15 minutes) to stream or download the audio. Ask for a new one when it expires; do not store it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Voice clips"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ url, expires_at, mime_type }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "url": "https://storage.example.com/tenants/…/voice-clips/6b0f….ogg?X-Amz-Signature=…",
                  "expires_at": "2026-10-28T15:19:05.000Z",
                  "mime_type": "audio/ogg"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/media-assets": {
      "get": {
        "summary": "List library files",
        "description": "The workspace library, newest first (at most 500; deleted files never appear). Every file carries `sendable` / `unsendable_reason` (`expired`, `consent_required` — it shows a person whose consent is not confirmed) and a short-lived `thumb_url` (15 minutes; images and videos). Filters: `kind` and `tag` (comma-separated, any), `topic_id`, `q` (free text over name, «cuándo usarlo», tags and topic names), `ai_enabled`, `status` (`live` | `expired` | `all`, default all).\n\nWith `conversation_id` every file also carries `sent_in_conversation`, `suggested` (linked to a topic the conversation’s contact is about) and `channel_ok` (the conversation’s channel can carry it).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 500,
              "description": "Comma-separated kinds: image, video, audio, document."
            },
            "required": false,
            "description": "Comma-separated kinds: image, video, audio, document.",
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 500,
              "description": "Comma-separated tags; a file qualifies when it carries any."
            },
            "required": false,
            "description": "Comma-separated tags; a file qualifies when it carries any.",
            "name": "tag",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "topic_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over name, «cuándo usarlo», tags and topic names."
            },
            "required": false,
            "description": "Free text over name, «cuándo usarlo», tags and topic names.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "ai_enabled",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "expired",
                "all"
              ],
              "description": "Default `all`."
            },
            "required": false,
            "description": "Default `all`.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 64,
              "description": "Adds `sent_in_conversation`, `suggested` and `channel_ok` for that conversation (the composer picker)."
            },
            "required": false,
            "description": "Adds `sent_in_conversation`, `suggested` and `channel_ok` for that conversation (the composer picker).",
            "name": "conversation_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Library files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "id": "3f6c2b1a-9d4e-4c7b-8a2f-1e0d9c8b7a65",
                    "kind": "image",
                    "name": "Plasma — antes/después 1",
                    "when_to_use": "Cuando pregunten por resultados del plasma",
                    "caption": "Resultado real tras 3 sesiones",
                    "tags": [
                      "antes-despues"
                    ],
                    "topics": [
                      {
                        "id": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                        "name": "Plasma rico en plaquetas",
                        "kind": "treatment"
                      }
                    ],
                    "models": [],
                    "transcript": null,
                    "mime_type": "image/jpeg",
                    "byte_size": 412345,
                    "duration_ms": null,
                    "width": 1080,
                    "height": 1350,
                    "page_count": null,
                    "original_filename": "IMG_2231.HEIC",
                    "ai_enabled": true,
                    "shows_person": true,
                    "speaker": null,
                    "is_intro": false,
                    "consent_confirmed_at": "2026-10-29T14:00:00.000Z",
                    "consent_confirmed_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                    "expires_at": null,
                    "expired": false,
                    "sendable": true,
                    "unsendable_reason": null,
                    "send_count": 3,
                    "last_sent_at": "2026-10-29T15:10:00.000Z",
                    "thumb_url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.thumb.webp?X-Amz-Signature=…",
                    "thumb_expires_at": "2026-10-29T15:25:00.000Z",
                    "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                    "created_at": "2026-10-29T13:58:00.000Z",
                    "updated_at": "2026-10-29T14:00:00.000Z"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a file to the library",
        "description": "Uploads `file` with its details. The kind (image, video, audio, document) is read from the bytes. Accepted files and caps: images JPG/PNG/WebP/HEIC ≤ 10 MB (auto-rotated, every metadata block — EXIF, GPS — removed, long edge ≤ 2560 px, stored as JPEG, or PNG when it was PNG, ≤ 5 MB); videos MP4/MOV/WebM ≤ 50 MB (converted to a WhatsApp-safe MP4 that must end ≤ 16 MB); PDF ≤ 16 MB; audio OGG/Opus, MP3, M4A/AAC, WAV, WebM, AMR ≤ 16 MB (transcribed unless `transcript` is sent). Refusals carry `details.code`: 413 `MEDIA_TOO_LARGE` (`details.limit_bytes`), 415 `MEDIA_UNSUPPORTED_TYPE` (an HEIC the server cannot decode included: convert it to JPG), 422 `MEDIA_EMPTY`. `name` defaults to the file name. `shows_person` defaults to true for images and videos in a healthcare workspace; while it is true and the consent is not confirmed nobody can send the file. A file identical to one already in the library is still created and the answer carries `duplicate_of: {id, name}`. 409 `MEDIA_LIBRARY_FULL` past 500 files; 422 for an unknown topic. Audio and video may name their `speaker` («Voz de», ≤ 80 characters; images and PDFs: 400 `MEDIA_SPEAKER_KIND`). An audio with a speaker may be that speaker’s `is_intro` («Presentación»): a short greeting the agent is never offered; the platform sends it once per conversation, right before the first audio of that speaker. 400 `MEDIA_INTRO_AUDIO_ONLY` / `MEDIA_INTRO_NEEDS_SPEAKER`; one live intro per speaker (case-insensitive): a second is 409 `MEDIA_INTRO_EXISTS` with `details.speaker`, `details.intro_id`, `details.intro_name`.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Defaults to the file name without its extension."
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "«Cuándo usarlo»: when this file fits. The agent reads it."
                  },
                  "caption": {
                    "type": "string",
                    "maxLength": 1024,
                    "description": "Default text sent with the file (not for audio)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the file (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 30,
                    "description": "Interest topics (treatment, model, product, service) this file illustrates; replaces the set. Unknown ids → 422."
                  },
                  "ai_enabled": {
                    "type": "boolean",
                    "description": "«El agente puede enviarlo». Default true."
                  },
                  "shows_person": {
                    "type": "boolean",
                    "description": "«Muestra a una persona / paciente». Default: true for images/videos in a healthcare workspace, else false. While true and consent is not confirmed, nobody can send the file."
                  },
                  "consent_confirmed": {
                    "type": "boolean",
                    "description": "The uploading member confirms the written consent of the person shown."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the file applies to, spelled as in the catalog (`product.model`). Empty = any model."
                  },
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Audio: what it says. When given, speech-to-text is skipped."
                  },
                  "speaker": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "«Voz de»: who speaks in this audio or video (e.g. \"Dra. Valentina\"), ≤ 80 characters, trimmed; empty = nobody in particular. Images and PDFs: 400 `MEDIA_SPEAKER_KIND`."
                  },
                  "is_intro": {
                    "type": "boolean",
                    "description": "«Presentación»: this audio is the speaker’s short greeting (\"Hola, soy …\"). Never offered to the agent: the platform sends it once per conversation, right before the first audio of that speaker. Audio with a `speaker` only (400 `MEDIA_INTRO_AUDIO_ONLY` / `MEDIA_INTRO_NEEDS_SPEAKER`); one per speaker (409 `MEDIA_INTRO_EXISTS`). Default false."
                  },
                  "file": {}
                }
              },
              "example": {
                "file": "(binary part)",
                "name": "Plasma — antes/después 1",
                "when_to_use": "Cuando pregunten por resultados del plasma",
                "tags": "antes-despues",
                "topic_ids": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                "shows_person": "true",
                "consent_confirmed": "true"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Defaults to the file name without its extension."
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "«Cuándo usarlo»: when this file fits. The agent reads it."
                  },
                  "caption": {
                    "type": "string",
                    "maxLength": 1024,
                    "description": "Default text sent with the file (not for audio)."
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the file (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 30,
                    "description": "Interest topics (treatment, model, product, service) this file illustrates; replaces the set. Unknown ids → 422."
                  },
                  "ai_enabled": {
                    "type": "boolean",
                    "description": "«El agente puede enviarlo». Default true."
                  },
                  "shows_person": {
                    "type": "boolean",
                    "description": "«Muestra a una persona / paciente». Default: true for images/videos in a healthcare workspace, else false. While true and consent is not confirmed, nobody can send the file."
                  },
                  "consent_confirmed": {
                    "type": "boolean",
                    "description": "The uploading member confirms the written consent of the person shown."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the file applies to, spelled as in the catalog (`product.model`). Empty = any model."
                  },
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Audio: what it says. When given, speech-to-text is skipped."
                  },
                  "speaker": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "«Voz de»: who speaks in this audio or video (e.g. \"Dra. Valentina\"), ≤ 80 characters, trimmed; empty = nobody in particular. Images and PDFs: 400 `MEDIA_SPEAKER_KIND`."
                  },
                  "is_intro": {
                    "type": "boolean",
                    "description": "«Presentación»: this audio is the speaker’s short greeting (\"Hola, soy …\"). Never offered to the agent: the platform sends it once per conversation, right before the first audio of that speaker. Audio with a `speaker` only (400 `MEDIA_INTRO_AUDIO_ONLY` / `MEDIA_INTRO_NEEDS_SPEAKER`); one per speaker (409 `MEDIA_INTRO_EXISTS`). Default false."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "plasma-1.jpg",
                "content": "/9j/4AAQSkZJRgABAQ…",
                "content_encoding": "base64",
                "name": "Plasma — antes/después 1",
                "when_to_use": "Cuando pregunten por resultados del plasma",
                "tags": [
                  "antes-despues"
                ],
                "shows_person": true,
                "consent_confirmed": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "id": "3f6c2b1a-9d4e-4c7b-8a2f-1e0d9c8b7a65",
                  "kind": "image",
                  "name": "Plasma — antes/después 1",
                  "when_to_use": "Cuando pregunten por resultados del plasma",
                  "caption": "Resultado real tras 3 sesiones",
                  "tags": [
                    "antes-despues"
                  ],
                  "topics": [
                    {
                      "id": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                      "name": "Plasma rico en plaquetas",
                      "kind": "treatment"
                    }
                  ],
                  "models": [],
                  "transcript": null,
                  "mime_type": "image/jpeg",
                  "byte_size": 412345,
                  "duration_ms": null,
                  "width": 1080,
                  "height": 1350,
                  "page_count": null,
                  "original_filename": "IMG_2231.HEIC",
                  "ai_enabled": true,
                  "shows_person": true,
                  "speaker": null,
                  "is_intro": false,
                  "consent_confirmed_at": "2026-10-29T14:00:00.000Z",
                  "consent_confirmed_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "expires_at": null,
                  "expired": false,
                  "sendable": true,
                  "unsendable_reason": null,
                  "send_count": 3,
                  "last_sent_at": "2026-10-29T15:10:00.000Z",
                  "thumb_url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.thumb.webp?X-Amz-Signature=…",
                  "thumb_expires_at": "2026-10-29T15:25:00.000Z",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-29T13:58:00.000Z",
                  "updated_at": "2026-10-29T14:00:00.000Z",
                  "duplicate_of": null
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/media-assets/speakers": {
      "get": {
        "summary": "List the speakers of the library",
        "description": "The distinct `speaker` values («Voz de») of the live files, case-insensitive, by name — the suggestions for a new upload. Each carries how many live files name it and the id of its «Presentación» (`intro_id`, null when it has none).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "responses": {
          "200": {
            "description": "Speakers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": [
                  {
                    "speaker": "Dra. Valentina",
                    "assets": 4,
                    "intro_id": "5a4b3c2d-1e0f-4a9b-8c7d-6e5f4a3b2c1d"
                  }
                ]
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/media-assets/{id}": {
      "get": {
        "summary": "Fetch one library file",
        "description": "The file with its details, topics, send counters and state. 404 for a deleted file or another workspace’s.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "3f6c2b1a-9d4e-4c7b-8a2f-1e0d9c8b7a65",
                  "kind": "image",
                  "name": "Plasma — antes/después 1",
                  "when_to_use": "Cuando pregunten por resultados del plasma",
                  "caption": "Resultado real tras 3 sesiones",
                  "tags": [
                    "antes-despues"
                  ],
                  "topics": [
                    {
                      "id": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                      "name": "Plasma rico en plaquetas",
                      "kind": "treatment"
                    }
                  ],
                  "models": [],
                  "transcript": null,
                  "mime_type": "image/jpeg",
                  "byte_size": 412345,
                  "duration_ms": null,
                  "width": 1080,
                  "height": 1350,
                  "page_count": null,
                  "original_filename": "IMG_2231.HEIC",
                  "ai_enabled": true,
                  "shows_person": true,
                  "speaker": null,
                  "is_intro": false,
                  "consent_confirmed_at": "2026-10-29T14:00:00.000Z",
                  "consent_confirmed_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "expires_at": null,
                  "expired": false,
                  "sendable": true,
                  "unsendable_reason": null,
                  "send_count": 3,
                  "last_sent_at": "2026-10-29T15:10:00.000Z",
                  "thumb_url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.thumb.webp?X-Amz-Signature=…",
                  "thumb_expires_at": "2026-10-29T15:25:00.000Z",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-29T13:58:00.000Z",
                  "updated_at": "2026-10-29T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a library file",
        "description": "Removes the file from the library and deletes its bytes at once (no undo). Conversations where it was already sent keep their own copy. The send history keeps pointing at it. To stop offering a file without losing it, set `expires_at` or turn `ai_enabled` off.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a library file",
        "description": "Changes the details; the bytes are replaced with `POST /media-assets/{id}/file`. `tags`, `topic_ids` and `models` replace their sets. `caption: null` and `expires_at: null` clear them. `consent_confirmed: true` records the calling member and now as the consent confirmation (a member only; an API key gets 422), `false` clears it. At least one field is required; an unknown topic is 422. `speaker: null` (or empty) clears the speaker and with it `is_intro`. Audio and video may name their `speaker` («Voz de», ≤ 80 characters; images and PDFs: 400 `MEDIA_SPEAKER_KIND`). An audio with a speaker may be that speaker’s `is_intro` («Presentación»): a short greeting the agent is never offered; the platform sends it once per conversation, right before the first audio of that speaker. 400 `MEDIA_INTRO_AUDIO_ONLY` / `MEDIA_INTRO_NEEDS_SPEAKER`; one live intro per speaker (case-insensitive): a second is 409 `MEDIA_INTRO_EXISTS` with `details.speaker`, `details.intro_id`, `details.intro_name`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "when_to_use": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "caption": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 60
                    },
                    "maxItems": 30,
                    "description": "Labels for finding the file (stored lower-cased). Multipart: repeat the field, or send a comma-separated or JSON-array string."
                  },
                  "topic_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 30,
                    "description": "Interest topics (treatment, model, product, service) this file illustrates; replaces the set. Unknown ids → 422."
                  },
                  "models": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "maxLength": 120
                    },
                    "maxItems": 100,
                    "description": "Model filter: the model names the file applies to, spelled as in the catalog (`product.model`). Empty = any model."
                  },
                  "transcript": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 8000,
                    "description": "Audio: the corrected transcript. Empty or null clears it."
                  },
                  "ai_enabled": {
                    "type": "boolean"
                  },
                  "shows_person": {
                    "type": "boolean"
                  },
                  "consent_confirmed": {
                    "type": "boolean",
                    "description": "`true` records the calling member and now as the consent confirmation; `false` clears it."
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "When the file stops being sendable (ISO 8601). Null = never. An expired file stays in the library, marked `expired`."
                  },
                  "speaker": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "«Voz de»: who speaks in this audio or video (e.g. \"Dra. Valentina\"), ≤ 80 characters, trimmed; empty = nobody in particular. Images and PDFs: 400 `MEDIA_SPEAKER_KIND`. null or empty clears it, and with it `is_intro`."
                  },
                  "is_intro": {
                    "type": "boolean",
                    "description": "«Presentación»: this audio is the speaker’s short greeting (\"Hola, soy …\"). Never offered to the agent: the platform sends it once per conversation, right before the first audio of that speaker. Audio with a `speaker` only (400 `MEDIA_INTRO_AUDIO_ONLY` / `MEDIA_INTRO_NEEDS_SPEAKER`); one per speaker (409 `MEDIA_INTRO_EXISTS`)."
                  }
                }
              },
              "example": {
                "when_to_use": "Cuando pregunten por resultados del plasma capilar",
                "topic_ids": [
                  "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586"
                ],
                "consent_confirmed": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "3f6c2b1a-9d4e-4c7b-8a2f-1e0d9c8b7a65",
                  "kind": "image",
                  "name": "Plasma — antes/después 1",
                  "when_to_use": "Cuando pregunten por resultados del plasma",
                  "caption": "Resultado real tras 3 sesiones",
                  "tags": [
                    "antes-despues"
                  ],
                  "topics": [
                    {
                      "id": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                      "name": "Plasma rico en plaquetas",
                      "kind": "treatment"
                    }
                  ],
                  "models": [],
                  "transcript": null,
                  "mime_type": "image/jpeg",
                  "byte_size": 412345,
                  "duration_ms": null,
                  "width": 1080,
                  "height": 1350,
                  "page_count": null,
                  "original_filename": "IMG_2231.HEIC",
                  "ai_enabled": true,
                  "shows_person": true,
                  "speaker": null,
                  "is_intro": false,
                  "consent_confirmed_at": "2026-10-29T14:00:00.000Z",
                  "consent_confirmed_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "expires_at": null,
                  "expired": false,
                  "sendable": true,
                  "unsendable_reason": null,
                  "send_count": 3,
                  "last_sent_at": "2026-10-29T15:10:00.000Z",
                  "thumb_url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.thumb.webp?X-Amz-Signature=…",
                  "thumb_expires_at": "2026-10-29T15:25:00.000Z",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-29T13:58:00.000Z",
                  "updated_at": "2026-10-29T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/media-assets/{id}/file": {
      "post": {
        "summary": "Replace the file of a library entry",
        "description": "Swaps the bytes and keeps the entry (id, name, topics, send history). The new file must be the same kind (409 `MEDIA_KIND_MISMATCH`). Dimensions, duration, page count and (audio) the transcript are re-computed; the consent is CLEARED when the file shows a person (it may be someone else). Accepted files and caps: images JPG/PNG/WebP/HEIC ≤ 10 MB (auto-rotated, every metadata block — EXIF, GPS — removed, long edge ≤ 2560 px, stored as JPEG, or PNG when it was PNG, ≤ 5 MB); videos MP4/MOV/WebM ≤ 50 MB (converted to a WhatsApp-safe MP4 that must end ≤ 16 MB); PDF ≤ 16 MB; audio OGG/Opus, MP3, M4A/AAC, WAV, WebM, AMR ≤ 16 MB (transcribed unless `transcript` is sent). Refusals carry `details.code`: 413 `MEDIA_TOO_LARGE` (`details.limit_bytes`), 415 `MEDIA_UNSUPPORTED_TYPE` (an HEIC the server cannot decode included: convert it to JPG), 422 `MEDIA_EMPTY`.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Audio: what the new file says. When given, speech-to-text is skipped."
                  },
                  "file": {}
                }
              },
              "example": {
                "file": "(binary part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  },
                  "transcript": {
                    "type": "string",
                    "maxLength": 8000,
                    "description": "Audio: what the new file says. When given, speech-to-text is skipped."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "plasma-1b.jpg",
                "content": "/9j/4AAQSkZJRgABAQ…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated file",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "id": "3f6c2b1a-9d4e-4c7b-8a2f-1e0d9c8b7a65",
                  "kind": "image",
                  "name": "Plasma — antes/después 1",
                  "when_to_use": "Cuando pregunten por resultados del plasma",
                  "caption": "Resultado real tras 3 sesiones",
                  "tags": [
                    "antes-despues"
                  ],
                  "topics": [
                    {
                      "id": "8e7d6c5b-4a39-4281-9f0e-d1c2b3a49586",
                      "name": "Plasma rico en plaquetas",
                      "kind": "treatment"
                    }
                  ],
                  "models": [],
                  "transcript": null,
                  "mime_type": "image/jpeg",
                  "byte_size": 412345,
                  "duration_ms": null,
                  "width": 1080,
                  "height": 1350,
                  "page_count": null,
                  "original_filename": "IMG_2231.HEIC",
                  "ai_enabled": true,
                  "shows_person": true,
                  "speaker": null,
                  "is_intro": false,
                  "consent_confirmed_at": "2026-10-29T14:00:00.000Z",
                  "consent_confirmed_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "expires_at": null,
                  "expired": false,
                  "sendable": true,
                  "unsendable_reason": null,
                  "send_count": 3,
                  "last_sent_at": "2026-10-29T15:10:00.000Z",
                  "thumb_url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.thumb.webp?X-Amz-Signature=…",
                  "thumb_expires_at": "2026-10-29T15:25:00.000Z",
                  "created_by": "2f1e0d9c-8b7a-4c6d-9e5f-1a2b3c4d5e6f",
                  "created_at": "2026-10-29T13:58:00.000Z",
                  "updated_at": "2026-10-29T14:00:00.000Z"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/media-assets/{id}/url": {
      "get": {
        "summary": "Get a short-lived URL to a library file",
        "description": "A signed URL (15 minutes) to view or download the file (`variant=original`, default) or its thumbnail (`variant=thumb`; 404 when the file has none — PDFs and audio). `disposition=attachment` asks the browser to download it. Ask for a new one when it expires; never store it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Media library"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "original",
                "thumb"
              ]
            },
            "required": false,
            "name": "variant",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "inline",
                "attachment"
              ]
            },
            "required": false,
            "name": "disposition",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "`{ url, expires_at, mime_type }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "url": "https://storage.example.com/tenants/…/media/3f6c…/l9x2.jpg?X-Amz-Signature=…",
                  "expires_at": "2026-10-29T15:25:00.000Z",
                  "mime_type": "image/jpeg"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces": {
      "get": {
        "summary": "List the workspace’s marketplace accounts",
        "description": "Every connection, with `secrets_preview` and `has_credentials` in place of the stored secrets, plus the current `verification_status`. Unpaginated.\n\n**`ad_quota`** (Yapo panel connections, `provider: \"yapo_panel\"`): how many ads the dealer’s Yapo plan allows and how many are active, as the last panel sync or panel publish read them: `{ used, allowed, plan_code, plan_name, checked_at }`. `used` and `allowed` are `null` when Yapo states no number (a free account has no stated allowance; the import plan has no cap); `plan_code` / `plan_name` are Yapo’s own (`U_PA_30` / \"Plan automotriz 30\"; `allowed` is the plan’s N). The whole field is `null` for every other portal and before the first read. A publish to a full plan is refused before anything is posted, with the publication in `error`: \"Tu plan de Yapo permite N avisos activos; quita uno o sube de plan\". Vitrina never buys a plan, an extra ad or a highlight.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "responses": {
          "200": {
            "description": "Marketplace accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "33333333-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "auth_type": "oauth2",
                      "name": "Mercado Libre Automotora Ejemplo",
                      "environment": "production",
                      "api_base_url": null,
                      "token_url": null,
                      "auth_url": null,
                      "seller_identifier": "123456789",
                      "secrets_preview": {
                        "access_token": "APP_••••9z"
                      },
                      "has_credentials": true,
                      "has_user_login": false,
                      "enabled": true,
                      "sync_enabled": true,
                      "publish_enabled": true,
                      "sync_interval_minutes": 60,
                      "verification_status": "verified",
                      "verification_checked_at": "2026-10-09T08:00:00.000Z",
                      "verification_error": null,
                      "ad_quota": null,
                      "created_at": "2026-09-01T12:00:00.000Z",
                      "updated_at": "2026-10-09T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces/{id}": {
      "get": {
        "summary": "Fetch one marketplace account",
        "description": "The account view: configuration, `verification_status`, and `has_credentials` / `secrets_preview` rather than the secrets themselves (`has_user_login` says whether a Veekls app user is stored). This is what to poll after a 202 from `/verify`.\n\n**`ad_quota`** (Yapo panel connections, `provider: \"yapo_panel\"`): how many ads the dealer’s Yapo plan allows and how many are active, as the last panel sync or panel publish read them: `{ used, allowed, plan_code, plan_name, checked_at }`. `used` and `allowed` are `null` when Yapo states no number (a free account has no stated allowance; the import plan has no cap); `plan_code` / `plan_name` are Yapo’s own (`U_PA_30` / \"Plan automotriz 30\"; `allowed` is the plan’s N). The whole field is `null` for every other portal and before the first read. A publish to a full plan is refused before anything is posted, with the publication in `error`: \"Tu plan de Yapo permite N avisos activos; quita uno o sube de plan\". Vitrina never buys a plan, an extra ad or a highlight.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The account",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "33333333-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "provider": "mercadolibre",
                    "auth_type": "oauth2",
                    "name": "Mercado Libre Automotora Ejemplo",
                    "environment": "production",
                    "api_base_url": null,
                    "token_url": null,
                    "auth_url": null,
                    "seller_identifier": "123456789",
                    "secrets_preview": {
                      "access_token": "APP_••••9z"
                    },
                    "has_credentials": true,
                    "has_user_login": false,
                    "enabled": true,
                    "sync_enabled": true,
                    "publish_enabled": true,
                    "sync_interval_minutes": 60,
                    "verification_status": "verified",
                    "verification_checked_at": "2026-10-09T08:00:00.000Z",
                    "verification_error": null,
                    "ad_quota": null,
                    "created_at": "2026-09-01T12:00:00.000Z",
                    "updated_at": "2026-10-09T08:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces/{id}/sync": {
      "post": {
        "summary": "Trigger a stock sync",
        "description": "Enqueues the import and answers **202** with `{ enqueued: true, force }`.\n\nThe job id is STABLE per account with no timestamp, which makes this idempotent while a run is in flight: a second click, or a click that races the scheduled tick, collapses into the ONE running sync and still answers 202. So a 202 means \"a sync is running for this account\", not \"your sync started\". Watch `/sync-runs` or `/health` for what actually happened. Retries three times with exponential backoff before the job is abandoned.\n\n`force: true` is the operator release for an account the inbound drop-guard blocked (ADR 0090 §5): a run whose candidate set is >= 8 units or >= 15% of active origin-owned stock aborts before deactivating anything and records `status: \"blocked\"`. It stays blocked on every scheduled retry until an operator confirms the drop is real (a dealer clearing their lot, not a feed rotation) and forces ONE run through with this flag — it bypasses only that threshold, never the empty-feed guard.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "force": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "force": false
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Sync enqueued or already in flight (`{ enqueued: true, force: boolean }`)"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces/{id}/sync-runs": {
      "get": {
        "summary": "Recent sync runs",
        "description": "The last 20 runs — the limit is fixed and not a query parameter. Each row is one attempt with its outcome, which is what to read after a 202 from `/sync`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Recent sync runs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000021",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                      "job_kind": "stock_sync",
                      "status": "success",
                      "started_at": "2026-10-09T08:00:00.000Z",
                      "finished_at": "2026-10-09T08:00:41.000Z",
                      "vehicles_imported": 2,
                      "vehicles_updated": 11,
                      "vehicles_deactivated": 1,
                      "error_message": null,
                      "raw_summary": {},
                      "created_at": "2026-10-09T08:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces/{id}/health": {
      "get": {
        "summary": "Per-job-kind health for the account",
        "description": "One row per job kind, each carrying **both** `last_run_at` and `last_success_at`.\n\nThe distinction is the entire point of this endpoint and must survive into whatever renders it. The account’s older `last_sync_*` columns conflated every scheduled job into one triple and could only say when something last RAN, which is how a week-long outage stayed invisible: the panel lead scrape stamped those columns every 30 minutes while the stock sync sharing them had been dead for days, and the UI showed a fresh green tick over a dead job. Alert on `last_success_at`; `last_run_at` only tells you something is still trying.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Health per job kind",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "accountId": "33333333-0000-4000-8000-000000000001",
                      "tenantId": "a1a1a1a1-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "name": "Mercado Libre Automotora Ejemplo",
                      "syncIntervalMinutes": 60,
                      "jobKind": "stock_sync",
                      "lastStatus": "success",
                      "lastRunAt": "2026-10-09T08:00:00.000Z",
                      "lastSuccessAt": "2026-10-09T08:00:00.000Z",
                      "lastError": null,
                      "accountCreatedAt": "2026-09-01T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/marketplaces/yapo/feed-swap": {
      "get": {
        "summary": "Yapo feed swap: ads to replace and progress",
        "description": "What connecting the Yapo XML feed would do to the workspace’s Yapo panel ads, for the consent step (vitrina-app#5956). `ads_to_replace` counts the live panel ads matched to cars still in stock that the feed would replace; `consent_required` is true when it is above 0, and then `message` is the sentence to show (Spanish). Send `yapo_feed_swap_consent: true` with the connect (`POST /marketplaces` with `provider: yapo`, or `PUT /marketplaces/{id}` re-enabling a feed).\n\n`swaps` counts this workspace’s swap rows per state: `awaiting_feed_ad` (the car’s feed ad is not live yet), `deleting_old_ad` (seen; the old ad’s delete is being retried), `timed_out` (48 h without it: the old ad is kept, the dealer was alerted, still watched), `swapped` (done), `old_ad_removed` (the car left stock during the swap) and `cancelled` (nothing to do: old ad already gone, or the feed was disconnected). Read-only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Marketplaces"
        ],
        "responses": {
          "200": {
            "description": "The swap preview",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ads_to_replace": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "consent_required": {
                          "type": "boolean"
                        },
                        "message": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "swaps": {
                          "type": "object",
                          "properties": {
                            "awaiting_feed_ad": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "deleting_old_ad": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "timed_out": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "swapped": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "old_ad_removed": {
                              "type": "integer",
                              "minimum": 0
                            },
                            "cancelled": {
                              "type": "integer",
                              "minimum": 0
                            }
                          },
                          "required": [
                            "awaiting_feed_ad",
                            "deleting_old_ad",
                            "timed_out",
                            "swapped",
                            "old_ad_removed",
                            "cancelled"
                          ]
                        }
                      },
                      "required": [
                        "ads_to_replace",
                        "consent_required",
                        "message",
                        "swaps"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ads_to_replace": 12,
                    "consent_required": true,
                    "message": "Al conectar el feed de Yapo se reemplazarán 12 avisos publicados desde el panel.",
                    "swaps": {
                      "awaiting_feed_ad": 3,
                      "deleting_old_ad": 0,
                      "timed_out": 0,
                      "swapped": 9,
                      "old_ad_removed": 0,
                      "cancelled": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/messaging-accounts/email/discover": {
      "post": {
        "summary": "Detect a mailbox's IMAP/SMTP settings from its address",
        "description": "Finds the IMAP and SMTP host, port and TLS mode for an email address the way Thunderbird and Outlook do, trying in order: the MX (Google / Microsoft 365 → `oauth` hint), the domain's autoconfig, Thunderbird's ISPDB, Outlook autodiscover (what cPanel hosting answers), SRV records, and finally the usual hostnames that accept a connection. The first source with a hit wins; each candidate names its `source`. **Nothing is persisted.**\n\nWithout `password`: the ranked `candidates`. With `password`: the candidates are also logged in to, in order (at most 4, about 15 s in all), and `verified` is the first whose IMAP AND SMTP logins passed; otherwise `attempts` carries each leg's error and `error_kind` — `auth` means the server answered and refused the password, `unreachable` / `timeout` / `tls` mean the server could not be reached. When `oauth` is set (Gmail / Microsoft 365), connect through the OAuth flow instead; no login is tried.\n\nThen connect with `POST /messaging-accounts/email`, sending `verified` (or the chosen candidate) with the password on both legs. Finding nothing is `200` with `candidates: []`. Rate-limited per workspace (`429`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 255,
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  }
                },
                "required": [
                  "email"
                ]
              },
              "example": {
                "email": "contacto@automotora-ejemplo.cl",
                "password": "contraseña-del-buzón"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ranked candidates and, with a password, the login results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "domain": {
                          "type": "string"
                        },
                        "oauth": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "gmail",
                            "outlook"
                          ]
                        },
                        "candidates": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "source": {
                                "type": "string",
                                "enum": [
                                  "oauth_hint",
                                  "autoconfig",
                                  "ispdb",
                                  "autodiscover",
                                  "srv",
                                  "guess"
                                ]
                              },
                              "imap": {
                                "type": "object",
                                "properties": {
                                  "host": {
                                    "type": "string"
                                  },
                                  "port": {
                                    "type": "integer"
                                  },
                                  "secure": {
                                    "type": "boolean",
                                    "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                  },
                                  "user": {
                                    "type": "string",
                                    "description": "Login name, placeholders already expanded"
                                  }
                                },
                                "required": [
                                  "host",
                                  "port",
                                  "secure",
                                  "user"
                                ]
                              },
                              "smtp": {
                                "type": "object",
                                "properties": {
                                  "host": {
                                    "type": "string"
                                  },
                                  "port": {
                                    "type": "integer"
                                  },
                                  "secure": {
                                    "type": "boolean",
                                    "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                  },
                                  "user": {
                                    "type": "string",
                                    "description": "Login name, placeholders already expanded"
                                  }
                                },
                                "required": [
                                  "host",
                                  "port",
                                  "secure",
                                  "user"
                                ]
                              }
                            },
                            "required": [
                              "source",
                              "imap",
                              "smtp"
                            ]
                          }
                        },
                        "verified": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "source": {
                              "type": "string",
                              "enum": [
                                "oauth_hint",
                                "autoconfig",
                                "ispdb",
                                "autodiscover",
                                "srv",
                                "guess"
                              ]
                            },
                            "imap": {
                              "type": "object",
                              "properties": {
                                "host": {
                                  "type": "string"
                                },
                                "port": {
                                  "type": "integer"
                                },
                                "secure": {
                                  "type": "boolean",
                                  "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                },
                                "user": {
                                  "type": "string",
                                  "description": "Login name, placeholders already expanded"
                                }
                              },
                              "required": [
                                "host",
                                "port",
                                "secure",
                                "user"
                              ]
                            },
                            "smtp": {
                              "type": "object",
                              "properties": {
                                "host": {
                                  "type": "string"
                                },
                                "port": {
                                  "type": "integer"
                                },
                                "secure": {
                                  "type": "boolean",
                                  "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                },
                                "user": {
                                  "type": "string",
                                  "description": "Login name, placeholders already expanded"
                                }
                              },
                              "required": [
                                "host",
                                "port",
                                "secure",
                                "user"
                              ]
                            }
                          },
                          "required": [
                            "source",
                            "imap",
                            "smtp"
                          ]
                        },
                        "attempts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "candidate": {
                                "type": "object",
                                "properties": {
                                  "source": {
                                    "type": "string",
                                    "enum": [
                                      "oauth_hint",
                                      "autoconfig",
                                      "ispdb",
                                      "autodiscover",
                                      "srv",
                                      "guess"
                                    ]
                                  },
                                  "imap": {
                                    "type": "object",
                                    "properties": {
                                      "host": {
                                        "type": "string"
                                      },
                                      "port": {
                                        "type": "integer"
                                      },
                                      "secure": {
                                        "type": "boolean",
                                        "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                      },
                                      "user": {
                                        "type": "string",
                                        "description": "Login name, placeholders already expanded"
                                      }
                                    },
                                    "required": [
                                      "host",
                                      "port",
                                      "secure",
                                      "user"
                                    ]
                                  },
                                  "smtp": {
                                    "type": "object",
                                    "properties": {
                                      "host": {
                                        "type": "string"
                                      },
                                      "port": {
                                        "type": "integer"
                                      },
                                      "secure": {
                                        "type": "boolean",
                                        "description": "true = implicit TLS (993/465); false = STARTTLS (143/587)"
                                      },
                                      "user": {
                                        "type": "string",
                                        "description": "Login name, placeholders already expanded"
                                      }
                                    },
                                    "required": [
                                      "host",
                                      "port",
                                      "secure",
                                      "user"
                                    ]
                                  }
                                },
                                "required": [
                                  "source",
                                  "imap",
                                  "smtp"
                                ]
                              },
                              "ok": {
                                "type": "boolean"
                              },
                              "imap": {
                                "type": "object",
                                "properties": {
                                  "ok": {
                                    "type": "boolean"
                                  },
                                  "error": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "error_kind": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "auth",
                                      "unreachable",
                                      "tls",
                                      "timeout",
                                      "other"
                                    ]
                                  }
                                },
                                "required": [
                                  "ok",
                                  "error",
                                  "error_kind"
                                ]
                              },
                              "smtp": {
                                "type": "object",
                                "properties": {
                                  "ok": {
                                    "type": "boolean"
                                  },
                                  "error": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "error_kind": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "enum": [
                                      "auth",
                                      "unreachable",
                                      "tls",
                                      "timeout",
                                      "other"
                                    ]
                                  }
                                },
                                "required": [
                                  "ok",
                                  "error",
                                  "error_kind"
                                ]
                              }
                            },
                            "required": [
                              "candidate",
                              "ok",
                              "imap",
                              "smtp"
                            ]
                          }
                        }
                      },
                      "required": [
                        "domain",
                        "oauth",
                        "candidates",
                        "verified",
                        "attempts"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "domain": "automotora-ejemplo.cl",
                    "oauth": null,
                    "candidates": [
                      {
                        "source": "autodiscover",
                        "imap": {
                          "host": "mail.automotora-ejemplo.cl",
                          "port": 993,
                          "secure": true,
                          "user": "contacto@automotora-ejemplo.cl"
                        },
                        "smtp": {
                          "host": "mail.automotora-ejemplo.cl",
                          "port": 465,
                          "secure": true,
                          "user": "contacto@automotora-ejemplo.cl"
                        }
                      }
                    ],
                    "verified": null,
                    "attempts": [
                      {
                        "candidate": {
                          "source": "autodiscover",
                          "imap": {
                            "host": "mail.automotora-ejemplo.cl",
                            "port": 993,
                            "secure": true,
                            "user": "contacto@automotora-ejemplo.cl"
                          },
                          "smtp": {
                            "host": "mail.automotora-ejemplo.cl",
                            "port": 465,
                            "secure": true,
                            "user": "contacto@automotora-ejemplo.cl"
                          }
                        },
                        "ok": false,
                        "imap": {
                          "ok": false,
                          "error": "Command failed: Authentication failed.",
                          "error_kind": "auth"
                        },
                        "smtp": {
                          "ok": false,
                          "error": "Invalid login: 535 Incorrect authentication data",
                          "error_kind": "auth"
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/messaging-accounts/email/test": {
      "post": {
        "summary": "Test IMAP/SMTP credentials",
        "description": "Dials both legs with the supplied credentials and reports each independently — **nothing is persisted**, so this is the safe way to let someone iterate on a mailbox configuration.\n\n**Run `POST /messaging-accounts/email/discover` first** to get the `imap` / `smtp` host, port and TLS mode instead of guessing them. If discover returns `oauth` (Gmail / Microsoft 365), do NOT use this route: have the owner connect it with the OAuth sign-in (Conectar Gmail / Microsoft 365 in the app), which needs no password.\n\nThe two legs fail independently: a mailbox can read (IMAP fine) and be unable to send (SMTP wrong), so read both results rather than treating the response as one boolean. `error_kind` says why a leg failed: `auth` = the server answered and refused the password, `unreachable` / `timeout` / `tls` = the server could not be reached with those settings. `imap.user` / `smtp.user` default to `email` when omitted, which is right for most providers.\n\n**The password is the mailbox owner’s real password.** Sending it through an AI chat puts it in that chat’s transcript — only do it when the owner asks you to. It is used for this one test, is never stored by this route, and is never returned or written to the audit log. Rate-limited per workspace (`429`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 255,
                    "format": "email"
                  },
                  "imap": {
                    "type": "object",
                    "properties": {
                      "host": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 255
                      },
                      "port": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 65535
                      },
                      "secure": {
                        "type": "boolean",
                        "default": true
                      },
                      "user": {
                        "type": "string",
                        "maxLength": 255
                      },
                      "password": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1024
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "password"
                    ]
                  },
                  "smtp": {
                    "type": "object",
                    "properties": {
                      "host": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 255
                      },
                      "port": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 65535
                      },
                      "secure": {
                        "type": "boolean",
                        "default": true
                      },
                      "user": {
                        "type": "string",
                        "maxLength": 255
                      },
                      "password": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1024
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "password"
                    ]
                  }
                },
                "required": [
                  "email",
                  "imap",
                  "smtp"
                ]
              },
              "example": {
                "email": "contacto@automotora-ejemplo.cl",
                "imap": {
                  "host": "mail.automotora-ejemplo.cl",
                  "port": 993,
                  "secure": true,
                  "password": "contraseña-del-buzón"
                },
                "smtp": {
                  "host": "mail.automotora-ejemplo.cl",
                  "port": 465,
                  "secure": true,
                  "password": "contraseña-del-buzón"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-leg connection results",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ok": {
                          "type": "boolean",
                          "description": "true only when BOTH legs passed"
                        },
                        "imap": {
                          "type": "object",
                          "properties": {
                            "ok": {
                              "type": "boolean"
                            },
                            "error": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "error_kind": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "auth",
                                "unreachable",
                                "tls",
                                "timeout",
                                "other"
                              ]
                            }
                          },
                          "required": [
                            "ok",
                            "error",
                            "error_kind"
                          ]
                        },
                        "smtp": {
                          "type": "object",
                          "properties": {
                            "ok": {
                              "type": "boolean"
                            },
                            "error": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "error_kind": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "enum": [
                                "auth",
                                "unreachable",
                                "tls",
                                "timeout",
                                "other"
                              ]
                            }
                          },
                          "required": [
                            "ok",
                            "error",
                            "error_kind"
                          ]
                        }
                      },
                      "required": [
                        "ok",
                        "imap",
                        "smtp"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ok": false,
                    "imap": {
                      "ok": true,
                      "error": null,
                      "error_kind": null
                    },
                    "smtp": {
                      "ok": false,
                      "error": "Invalid login: 535 Incorrect authentication data",
                      "error_kind": "auth"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/messaging-accounts/email": {
      "post": {
        "summary": "Connect a mailbox over IMAP/SMTP",
        "description": "Creates the `email_imap` account and stores the credentials encrypted. Same body as `/email/test` plus naming. The connection is re-tested server-side before anything is created, so **a failed connection answers 422 with the per-leg errors** (`imap` / `smtp`, each with `error_kind`) and creates nothing.\n\nFlow: `POST /messaging-accounts/email/discover` (finds the settings; with the password it also returns the `verified` candidate), optionally `POST /messaging-accounts/email/test`, then this route with the `verified` (or chosen) candidate’s `imap` / `smtp` and the password on both legs.\n\nThis is the raw-credential path. **Prefer OAuth whenever discover returns `oauth`** (Gmail, Microsoft 365): connect those through the OAuth sign-in in the app (Conectar Gmail / Microsoft 365), which needs no password.\n\n**The password is the mailbox owner’s real password.** Sending it through an AI chat puts it in that chat’s transcript — only do it when the owner asks you to. It is stored encrypted, **never returned** (the account in the response carries no secrets, and later reads redact them) and never written to the audit log. Rate-limited per workspace (`429`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "MessagingAccounts"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "maxLength": 255,
                    "format": "email"
                  },
                  "imap": {
                    "type": "object",
                    "properties": {
                      "host": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 255
                      },
                      "port": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 65535
                      },
                      "secure": {
                        "type": "boolean",
                        "default": true
                      },
                      "user": {
                        "type": "string",
                        "maxLength": 255
                      },
                      "password": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1024
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "password"
                    ]
                  },
                  "smtp": {
                    "type": "object",
                    "properties": {
                      "host": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 255
                      },
                      "port": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 65535
                      },
                      "secure": {
                        "type": "boolean",
                        "default": true
                      },
                      "user": {
                        "type": "string",
                        "maxLength": 255
                      },
                      "password": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 1024
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "password"
                    ]
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "from_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "assignee_ai_agent_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "purpose": {
                    "type": "string",
                    "enum": [
                      "conversations",
                      "bank_reconciliation"
                    ],
                    "description": "What the channel is for. `bank_reconciliation` («Solo conciliación bancaria», email mailboxes only): bank transfer notifications feed the bank-movement ledger; every other message is dropped (no conversation, no notification), the channel never has an AI agent and never sends. Default `conversations`."
                  }
                },
                "required": [
                  "email",
                  "imap",
                  "smtp"
                ]
              },
              "example": {
                "email": "contacto@automotora-ejemplo.cl",
                "name": "Correo de contacto",
                "imap": {
                  "host": "mail.automotora-ejemplo.cl",
                  "port": 993,
                  "secure": true,
                  "password": "contraseña-del-buzón"
                },
                "smtp": {
                  "host": "mail.automotora-ejemplo.cl",
                  "port": 465,
                  "secure": true,
                  "password": "contraseña-del-buzón"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The connected email account (no secrets)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "3f6d2c1e-8a4b-4d7e-9c10-5b2a7e9f0a11",
                    "name": "Correo de contacto",
                    "kind": "email_imap",
                    "channels": [
                      "email"
                    ],
                    "enabled": true,
                    "config": {
                      "connected_via": "imap_smtp",
                      "email": "contacto@automotora-ejemplo.cl",
                      "from_name": null,
                      "imap": {
                        "host": "mail.automotora-ejemplo.cl",
                        "port": 993,
                        "secure": true,
                        "user": "contacto@automotora-ejemplo.cl"
                      },
                      "smtp": {
                        "host": "mail.automotora-ejemplo.cl",
                        "port": 465,
                        "secure": true,
                        "user": "contacto@automotora-ejemplo.cl"
                      }
                    },
                    "created_at": "2026-10-16T14:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "The IMAP and/or SMTP leg failed; nothing was created"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/products": {
      "get": {
        "summary": "Search products",
        "description": "`q` is matched WORD BY WORD, accent- and case-insensitively: every word must appear in the SEARCH INDEX (title, SKU, brand, model — not `description`) or be an option value of one of the product’s active variants, with Spanish gender/plural folded (`negra` finds `Negro`). Question words and option names (`talla`, `color`, `hay`) are ignored, so `polera negra talla M` finds the polera that has a Negro × M variant.\n\nEach row carries `origin` (`native` | `shopify` | `woocommerce`), `external_id`, and `variants[]` — every variant with its own `sku`, `price_clp` (null = the product’s price), `available_quantity`, `available` and a display `label`. A product’s `available_quantity` is the sum of its active variants’ stock when it has variants.\n\nRead `available_quantity` is what can be SOLD now: `on_hand_quantity` (the counted units, what a stock sync writes) minus `held_quantity` (units kept 15 minutes for unpaid storefront checkouts, `POST /orders/checkout`).\n\nTwo defaults worth stating because they differ. `active` defaults to **true** — inactive products are hidden unless you ask for them. `in_stock` defaults to **false**, so products with `available_quantity: 0` ARE included; pass `in_stock=true` for the sellable-today list. `sort` defaults to `price_asc`.\n\nThe response carries rows and nothing else — there is no `total`, so paginate by asking for `limit + 1` rather than by counting.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string"
            },
            "required": false,
            "name": "brand",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ]
            },
            "required": false,
            "name": "min_price",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "number",
                "null"
              ]
            },
            "required": false,
            "name": "max_price",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "new",
                "used"
              ]
            },
            "required": false,
            "name": "condition",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "in_stock",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "price_asc",
                "price_desc",
                "recent"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "origin_integration_id": null,
                      "source": "manual",
                      "origin": "native",
                      "external_id": null,
                      "title": "Filtro de aceite Ejemplo FA-100",
                      "sku": "FA-100",
                      "brand": "Ejemplo",
                      "model": "FA-100",
                      "category_hint": "Filtros de aceite",
                      "category_id": null,
                      "price_amount": 12990,
                      "price_clp": 12990,
                      "price_currency": "CLP",
                      "available_quantity": 14,
                      "condition": "new",
                      "listing_type": null,
                      "description": "Filtro de aceite para motores bencineros de 1.4 a 1.6 litros.",
                      "photos": [
                        {
                          "url": "https://cdn.example.cl/productos/fa-100-1.jpg",
                          "order": 0
                        }
                      ],
                      "attributes": {
                        "rosca": "M20x1.5"
                      },
                      "tags": [
                        "filtros",
                        "mantencion"
                      ],
                      "searchable_text": "filtro de aceite ejemplo fa-100 fa-100 ejemplo fa-100",
                      "active": true,
                      "published_at": null,
                      "last_seen_at": null,
                      "created_at": "2026-10-01T14:20:00.000Z",
                      "updated_at": "2026-10-01T14:20:00.000Z",
                      "variants": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a product",
        "description": "Every field is optional — a product with no title is creatable. Validation of what a given PORTAL requires happens at publish time, not here, so a product can be stored complete enough for the agent to quote and still be rejected by Mercado Libre.\n\nPass `variants[]` to create the product WITH its variants in one transaction (3 tallas × 2 colores = 6 variants, each with its own `options`, `sku`, `price_amount` and `available_quantity`). With variants the stock lives on them, so a product-level `available_quantity` is refused (400). Products created here are `origin: \"native\"`; store products arrive through their sync.\n\nDefaults: `available_quantity: 0` (so a new product is out of stock until you say otherwise), `condition: \"new\"`, `price_currency: \"CLP\"`, `active: true`. `category_hint` is free text that drives the portal’s category prediction; set `category_id` only to override the predictor outright. Answers 201.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "sku": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "category_hint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "category_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "price_currency": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "available_quantity": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "condition": {
                    "type": "string",
                    "enum": [
                      "new",
                      "used"
                    ]
                  },
                  "listing_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "order": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "focal": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "x": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "y": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "manual",
                                "auto"
                              ],
                              "default": "manual"
                            }
                          },
                          "required": [
                            "x",
                            "y"
                          ]
                        }
                      },
                      "required": [
                        "url"
                      ]
                    }
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "variants": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "sku": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 120
                        },
                        "barcode": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 120
                        },
                        "title": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 255
                        },
                        "options": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "name": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 60
                              },
                              "value": {
                                "type": "string",
                                "minLength": 1,
                                "maxLength": 120
                              }
                            },
                            "required": [
                              "name",
                              "value"
                            ]
                          },
                          "maxItems": 5
                        },
                        "attributes": {
                          "type": "object",
                          "additionalProperties": {
                            "anyOf": [
                              {
                                "type": "string",
                                "maxLength": 300
                              },
                              {
                                "type": "number"
                              },
                              {
                                "type": "boolean"
                              }
                            ]
                          }
                        },
                        "price_amount": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "minimum": 0
                        },
                        "available_quantity": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "inventory_tracked": {
                          "type": "boolean"
                        },
                        "inventory_policy": {
                          "type": "string",
                          "enum": [
                            "deny",
                            "continue"
                          ]
                        },
                        "image_url": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 2048,
                          "format": "uri"
                        },
                        "position": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "active": {
                          "type": "boolean"
                        }
                      }
                    },
                    "maxItems": 100
                  }
                }
              },
              "example": {
                "title": "Filtro de aceite Ejemplo FA-100",
                "sku": "FA-100",
                "brand": "Ejemplo",
                "model": "FA-100",
                "category_hint": "Filtros de aceite",
                "price_amount": 12990,
                "price_currency": "CLP",
                "available_quantity": 14,
                "condition": "new",
                "description": "Filtro de aceite para motores bencineros de 1.4 a 1.6 litros.",
                "photos": [
                  {
                    "url": "https://cdn.example.cl/productos/fa-100-1.jpg",
                    "order": 0
                  }
                ],
                "attributes": {
                  "rosca": "M20x1.5"
                },
                "tags": [
                  "filtros",
                  "mantencion"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "origin": "native",
                    "external_id": null,
                    "title": "Filtro de aceite Ejemplo FA-100",
                    "sku": "FA-100",
                    "brand": "Ejemplo",
                    "model": "FA-100",
                    "category_hint": "Filtros de aceite",
                    "category_id": null,
                    "price_amount": 12990,
                    "price_clp": 12990,
                    "price_currency": "CLP",
                    "available_quantity": 14,
                    "condition": "new",
                    "listing_type": null,
                    "description": "Filtro de aceite para motores bencineros de 1.4 a 1.6 litros.",
                    "photos": [
                      {
                        "url": "https://cdn.example.cl/productos/fa-100-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {
                      "rosca": "M20x1.5"
                    },
                    "tags": [
                      "filtros",
                      "mantencion"
                    ],
                    "searchable_text": "filtro de aceite ejemplo fa-100 fa-100 ejemplo fa-100",
                    "active": true,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-01T14:20:00.000Z",
                    "variants": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/products/publications": {
      "get": {
        "summary": "Publication state for many products at once",
        "description": "The batched read behind the inventory list: one request per page instead of one per row. `ids` is a comma-separated list of up to **100** uuids and the response is a flat array of publication rows across all of them — group by `product_id` client-side.\n\nThere are no per-id 404 semantics: an id in another workspace, or one that does not exist, simply contributes no rows. So a short answer means \"these have no publications\", not \"one of your ids was wrong\".\n\nRegistered before `/{id}` so `publications` is never read as a product id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Publication rows across the requested products",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000011",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "external_id": "MLC1234567890",
                      "status": "published",
                      "last_synced_at": "2026-10-02T09:15:00.000Z",
                      "last_published_at": "2026-10-02T09:15:00.000Z",
                      "last_error": null,
                      "raw_provider_payload": null,
                      "created_at": "2026-10-02T09:15:00.000Z",
                      "updated_at": "2026-10-02T09:15:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/products/{id}": {
      "get": {
        "summary": "Fetch one product",
        "description": "The canonical row, including `photos`, `attributes` and `tags`. Portal listing state is NOT here — read `GET /products/{id}/publications` for that.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "origin": "native",
                    "external_id": null,
                    "title": "Filtro de aceite Ejemplo FA-100",
                    "sku": "FA-100",
                    "brand": "Ejemplo",
                    "model": "FA-100",
                    "category_hint": "Filtros de aceite",
                    "category_id": null,
                    "price_amount": 12990,
                    "price_clp": 12990,
                    "price_currency": "CLP",
                    "available_quantity": 14,
                    "condition": "new",
                    "listing_type": null,
                    "description": "Filtro de aceite para motores bencineros de 1.4 a 1.6 litros.",
                    "photos": [
                      {
                        "url": "https://cdn.example.cl/productos/fa-100-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {
                      "rosca": "M20x1.5"
                    },
                    "tags": [
                      "filtros",
                      "mantencion"
                    ],
                    "searchable_text": "filtro de aceite ejemplo fa-100 fa-100 ejemplo fa-100",
                    "active": true,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-01T14:20:00.000Z",
                    "variants": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a product",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied and at least one is required. `photos`, `attributes` and `tags` are REPLACED wholesale when present, never merged.\n\n**Sending `null` does not clear a field.** Nulls are dropped on the way through, so a field can be set and changed but not emptied here.\n\n**Send `title`, `sku`, `brand` and `model` together or not at all.** Touching any one of the four rebuilds the search index from the four values IN THIS REQUEST, so patching just the title leaves a product that is no longer findable by its SKU or brand until they are written again. Other fields do not disturb the index.\n\nThis updates the canonical row only — live portal listings keep the old values until you `POST /products/{id}/publish` again.\n\n**A synced product (`origin` shopify / woocommerce) answers 409** for the fields its store owns (`title`, `description`, `sku`, `brand`, `price_amount`, `price_currency`, `available_quantity`, `photos`, `tags`, `active`) — the next sync would overwrite them; edit them in the store. Vitrina-only fields stay editable. A product with variants refuses `available_quantity` (400): edit the variant instead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "sku": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "model": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "category_hint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "category_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "price_currency": {
                    "type": "string",
                    "maxLength": 8
                  },
                  "available_quantity": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "condition": {
                    "type": "string",
                    "enum": [
                      "new",
                      "used"
                    ]
                  },
                  "listing_type": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "photos": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "url": {
                          "type": "string",
                          "format": "uri"
                        },
                        "order": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "focal": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "x": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "y": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "manual",
                                "auto"
                              ],
                              "default": "manual"
                            }
                          },
                          "required": [
                            "x",
                            "y"
                          ]
                        }
                      },
                      "required": [
                        "url"
                      ]
                    }
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {}
                  },
                  "tags": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "title": "Filtro de aceite Ejemplo FA-100 (pack x2)",
                "sku": "FA-100",
                "brand": "Ejemplo",
                "model": "FA-100",
                "available_quantity": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "origin_integration_id": null,
                    "source": "manual",
                    "origin": "native",
                    "external_id": null,
                    "title": "Filtro de aceite Ejemplo FA-100 (pack x2)",
                    "sku": "FA-100",
                    "brand": "Ejemplo",
                    "model": "FA-100",
                    "category_hint": "Filtros de aceite",
                    "category_id": null,
                    "price_amount": 12990,
                    "price_clp": 12990,
                    "price_currency": "CLP",
                    "available_quantity": 20,
                    "condition": "new",
                    "listing_type": null,
                    "description": "Filtro de aceite para motores bencineros de 1.4 a 1.6 litros.",
                    "photos": [
                      {
                        "url": "https://cdn.example.cl/productos/fa-100-1.jpg",
                        "order": 0
                      }
                    ],
                    "attributes": {
                      "rosca": "M20x1.5"
                    },
                    "tags": [
                      "filtros",
                      "mantencion"
                    ],
                    "searchable_text": "filtro de aceite ejemplo fa-100 fa-100 ejemplo fa-100",
                    "active": true,
                    "published_at": null,
                    "last_seen_at": null,
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-03T10:05:00.000Z",
                    "variants": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a product",
        "description": "A hard delete of the canonical row; its publication records cascade away with it.\n\n**It does not take the listings down.** Nothing here calls the portal, so a published product deleted this way stays live and purchasable on Mercado Libre while the local record that knew its external id is gone — leaving no handle to remove it with afterwards. `DELETE /products/{id}/publications/{pubId}` on each portal FIRST, then delete the product.\n\nIts variants are deleted with it. A synced product belongs to its store and answers 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/products/{id}/variants": {
      "post": {
        "summary": "Add a variant to a product",
        "description": "One purchasable variant (a talla × color combination) with its own `options` (ordered `[{name, value}]`), `sku`, `price_amount` (whole CLP; null = the product’s price) and `available_quantity`. The product’s `available_quantity` becomes the sum of its active variants’ stock. `inventory_tracked: false` or `inventory_policy: \"continue\"` keep a variant sellable at zero stock. `attributes` is a small flat map of per-variant facts (string → string | number | boolean; at most 20 keys, 2 KB), e.g. `{\"battery_min_pct\": 99, \"battery_max_pct\": 100}`; it reads `{}` when none.\n\nOnly a native product’s variants are edited here: a synced product answers 409. Answers 201 with the variant (`available`, `label`).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sku": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "barcode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60
                        },
                        "value": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        }
                      },
                      "required": [
                        "name",
                        "value"
                      ]
                    },
                    "maxItems": 5
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 300
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "available_quantity": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "inventory_tracked": {
                    "type": "boolean"
                  },
                  "inventory_policy": {
                    "type": "string",
                    "enum": [
                      "deny",
                      "continue"
                    ]
                  },
                  "image_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "sku": "POL-NEGRO-M",
                "options": [
                  {
                    "name": "Color",
                    "value": "Negro"
                  },
                  {
                    "name": "Talla",
                    "value": "M"
                  }
                ],
                "attributes": {
                  "battery_min_pct": 99,
                  "battery_max_pct": 100
                },
                "available_quantity": 4
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created variant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000031",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "external_id": null,
                    "sku": "POL-NEGRO-M",
                    "barcode": null,
                    "title": null,
                    "options": [
                      {
                        "name": "Color",
                        "value": "Negro"
                      },
                      {
                        "name": "Talla",
                        "value": "M"
                      }
                    ],
                    "option_tokens": [
                      "m",
                      "negr"
                    ],
                    "attributes": {
                      "battery_min_pct": 99,
                      "battery_max_pct": 100
                    },
                    "price_clp": null,
                    "available_quantity": 4,
                    "inventory_tracked": true,
                    "inventory_policy": "deny",
                    "image_url": null,
                    "position": 0,
                    "active": true,
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-01T14:20:00.000Z",
                    "available": true,
                    "label": "Negro / M"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/products/{id}/variants/{variantId}": {
      "delete": {
        "summary": "Delete a variant",
        "description": "A hard delete; the product’s stock is re-totalled from the remaining variants. To stop selling a variant but keep it, PATCH `active: false` instead. A synced product answers 409.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "variantId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a variant",
        "description": "Applies only the fields present. `options` and `attributes` are replaced wholesale (send the full map; `{}` clears it). Setting `available_quantity` updates the product’s rolled-up stock in the same write. A synced product answers 409; a variant of another product answers 404.\n\n`available_quantity` WRITTEN here is the ON-HAND count (the units you have). Units held for unpaid storefront checkouts are separate rows a write never erases: the response (and every read) returns `on_hand_quantity`, `held_quantity` and `available_quantity` = on hand − held.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "variantId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sku": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "barcode": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 255
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60
                        },
                        "value": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120
                        }
                      },
                      "required": [
                        "name",
                        "value"
                      ]
                    },
                    "maxItems": 5
                  },
                  "attributes": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 300
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "boolean"
                        }
                      ]
                    }
                  },
                  "price_amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0
                  },
                  "available_quantity": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "inventory_tracked": {
                    "type": "boolean"
                  },
                  "inventory_policy": {
                    "type": "string",
                    "enum": [
                      "deny",
                      "continue"
                    ]
                  },
                  "image_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2048,
                    "format": "uri"
                  },
                  "position": {
                    "type": "integer",
                    "minimum": 0
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "available_quantity": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated variant",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000031",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "external_id": null,
                    "sku": "POL-NEGRO-M",
                    "barcode": null,
                    "title": null,
                    "options": [
                      {
                        "name": "Color",
                        "value": "Negro"
                      },
                      {
                        "name": "Talla",
                        "value": "M"
                      }
                    ],
                    "option_tokens": [
                      "m",
                      "negr"
                    ],
                    "attributes": {
                      "battery_min_pct": 99,
                      "battery_max_pct": 100
                    },
                    "price_clp": null,
                    "available_quantity": 2,
                    "inventory_tracked": true,
                    "inventory_policy": "deny",
                    "image_url": null,
                    "position": 0,
                    "active": true,
                    "created_at": "2026-10-01T14:20:00.000Z",
                    "updated_at": "2026-10-03T10:05:00.000Z",
                    "available": true,
                    "label": "Negro / M"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/products/{id}/publications": {
      "get": {
        "summary": "Where this product is listed",
        "description": "One row per portal the product has been pushed to, carrying `status`, the portal’s `external_id`, `last_published_at` and `last_error`.\n\nA row with `status: \"deleted\"` is a TOMBSTONE, not a live listing — it records that the product was once published there and has since been taken down. Count only what you mean to.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Publication rows for this product",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "7a7a7a7a-0000-4000-8000-000000000011",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                      "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "external_id": "MLC1234567890",
                      "status": "published",
                      "last_synced_at": "2026-10-02T09:15:00.000Z",
                      "last_published_at": "2026-10-02T09:15:00.000Z",
                      "last_error": null,
                      "raw_provider_payload": null,
                      "created_at": "2026-10-02T09:15:00.000Z",
                      "updated_at": "2026-10-02T09:15:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/products/{id}/publish": {
      "post": {
        "summary": "Publish to one or more portals",
        "description": "Pushes the canonical product to each of `integration_ids`, in order, and answers **200 with a per-portal outcome array** — `status` is `published` or `error` for each, with the message on the failing ones. A 200 therefore does NOT mean everything published; read the array.\n\nRe-publishing is how you push an edit: an existing publication supplies its `external_id`, so the portal listing is updated rather than duplicated.\n\nPer-portal failures are recorded and kept going, but an `integration_id` the workspace does not own **aborts the whole call with a 400** — and portals earlier in the array have already been published by then. That partial state is real and is not rolled back, so validate the ids before sending rather than after.\n\nA portal whose provider has no product seam, or one with publishing disabled, comes back as a per-portal `error`. The product’s `published_at` is stamped on the first success and never cleared.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integration_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "integration_ids"
                ]
              },
              "example": {
                "integration_ids": [
                  "33333333-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-portal publish outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "integration_id": "33333333-0000-4000-8000-000000000001",
                      "provider": "mercadolibre",
                      "status": "published",
                      "external_id": "MLC1234567890",
                      "publication": {
                        "id": "7a7a7a7a-0000-4000-8000-000000000011",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                        "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                        "provider": "mercadolibre",
                        "external_id": "MLC1234567890",
                        "status": "published",
                        "last_synced_at": "2026-10-02T09:15:00.000Z",
                        "last_published_at": "2026-10-02T09:15:00.000Z",
                        "last_error": null,
                        "raw_provider_payload": null,
                        "created_at": "2026-10-02T09:15:00.000Z",
                        "updated_at": "2026-10-02T09:15:00.000Z"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/products/{id}/publications/{pubId}": {
      "delete": {
        "summary": "Take a listing down",
        "description": "Removes the listing at the portal and marks the publication `deleted`. Answers **200 with the tombstoned row**, not 204 — the record survives so the history of where a product was listed does.\n\nRemote removal is best-effort: if the publication never got an `external_id`, or the provider has no product seam, the local row is still marked `deleted`. Check the portal itself when it matters.\n\nThe `{id}` in the path is not used to locate the publication — `pubId` identifies it on its own, and the workspace check is against that row.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Products"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "pubId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The tombstoned publication",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "7a7a7a7a-0000-4000-8000-000000000011",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "product_id": "7a7a7a7a-0000-4000-8000-000000000001",
                    "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                    "provider": "mercadolibre",
                    "external_id": "MLC1234567890",
                    "status": "deleted",
                    "last_synced_at": "2026-10-02T09:15:00.000Z",
                    "last_published_at": "2026-10-02T09:15:00.000Z",
                    "last_error": null,
                    "raw_provider_payload": null,
                    "created_at": "2026-10-02T09:15:00.000Z",
                    "updated_at": "2026-10-04T11:30:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/properties": {
      "get": {
        "summary": "Search the cartera",
        "description": "Live properties, newest first, every filter ANDed. Prices filter across currencies: «2 dormitorios en Ñuñoa bajo UF 5.000» is `comuna=Ñuñoa&min_bedrooms=2&max_price=5000&price_currency=UF`, and it also finds properties priced in CLP, converted with the day’s UF (`meta.uf_value`). Without a known UF value, a price bound matches only rows in its own currency. Requires `marketplace:read`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over title, comuna, sector and display id."
            },
            "required": false,
            "description": "Free text over title, comuna, sector and display id.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "venta",
                "arriendo"
              ],
              "description": "What the buyer wants: `venta` also matches venta_y_arriendo, and so does `arriendo`. The price bounds then apply to that operation’s price."
            },
            "required": false,
            "description": "What the buyer wants: `venta` also matches venta_y_arriendo, and so does `arriendo`. The price bounds then apply to that operation’s price.",
            "name": "operation",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "departamento",
                  "casa",
                  "oficina",
                  "local_comercial",
                  "terreno",
                  "parcela",
                  "bodega",
                  "estacionamiento",
                  "industrial",
                  "otro"
                ],
                "description": "Tipo de propiedad."
              },
              "maxItems": 50
            },
            "required": false,
            "name": "property_type",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "disponible",
                  "bloqueada",
                  "reservada",
                  "promesada",
                  "vendida",
                  "arrendada",
                  "pausada"
                ],
                "description": "Estado: disponible | bloqueada | reservada | promesada | vendida | arrendada | pausada. `bloqueada` and `promesada` only occur on a proyecto unidad."
              },
              "maxItems": 50
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean",
              "description": "Also list the unidades of the workspace’s proyectos (Módulo Proyectos). Default false: the cartera only."
            },
            "required": false,
            "description": "Also list the unidades of the workspace’s proyectos (Módulo Proyectos). Default false: the cartera only.",
            "name": "include_units",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "maxItems": 50,
              "description": "Comma-separated, accent- and case-insensitive."
            },
            "required": false,
            "description": "Comma-separated, accent- and case-insensitive.",
            "name": "comuna",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_bedrooms",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_bathrooms",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_parking",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean"
            },
            "required": false,
            "name": "pets_allowed",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean"
            },
            "required": false,
            "name": "furnished",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "broker_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            },
            "required": false,
            "name": "min_price",
            "in": "query"
          },
          {
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0
            },
            "required": false,
            "name": "max_price",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "UF",
                "CLP"
              ],
              "description": "Unit of min_price/max_price (default CLP). Rows priced in the other currency are converted with the day’s UF."
            },
            "required": false,
            "description": "Unit of min_price/max_price (default CLP). Rows priced in the other currency are converted with the day’s UF.",
            "name": "price_currency",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Properties",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Property"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        },
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ],
                          "description": "The UF value the conversions used (CLP per UF, its day)."
                        }
                      },
                      "required": [
                        "pagination",
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000001",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "display_id": "I-12",
                      "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                      "description": "Luminoso, orientación norte, a pasos del metro.",
                      "operation": "venta",
                      "property_type": "departamento",
                      "status": "disponible",
                      "region": "Metropolitana",
                      "comuna": "Ñuñoa",
                      "sector": "Plaza Ñuñoa",
                      "address": "Av. Irarrázaval 0000, depto 000",
                      "sale_price_amount": 4800,
                      "sale_price_currency": "UF",
                      "rent_price_amount": null,
                      "rent_price_currency": null,
                      "common_expenses_clp": 120000,
                      "property_tax_clp": 85000,
                      "bedrooms": 2,
                      "bathrooms": 2,
                      "usable_area_m2": 62,
                      "total_area_m2": 70,
                      "parking_spaces": 1,
                      "storage_units": 1,
                      "orientation": "norte",
                      "floor": 8,
                      "year_built": 2018,
                      "furnished": false,
                      "pets_allowed": true,
                      "photos": [
                        "https://example.com/i-12/1.jpg"
                      ],
                      "broker_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "owner_name": "Propietaria de ejemplo",
                      "owner_phone": "+56900000000",
                      "owner_email": "propietaria@example.com",
                      "source": "manual",
                      "external_ref": null,
                      "protected_fields": [],
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-14T12:00:00.000Z",
                      "updated_at": "2026-10-14T12:00:00.000Z",
                      "sale_price": {
                        "amount": 4800,
                        "currency": "UF",
                        "amount_clp": 189120000,
                        "amount_uf": 4800
                      },
                      "rent_price": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    },
                    "uf_value": {
                      "clp": 39400,
                      "date": "2026-10-14"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a property to the cartera",
        "description": "A price belongs to an operation the property is offered for (`sale_price_*` needs venta, `rent_price_*` needs arriendo; venta_y_arriendo carries both), and each price carries its currency (UF or CLP). The display id (`I-n`) is allocated by the server. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePropertyBody"
              },
              "example": {
                "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                "operation": "venta",
                "property_type": "departamento",
                "comuna": "Ñuñoa",
                "sale_price_amount": 4800,
                "sale_price_currency": "UF",
                "bedrooms": 2,
                "bathrooms": 2
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-12",
                    "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                    "description": "Luminoso, orientación norte, a pasos del metro.",
                    "operation": "venta",
                    "property_type": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "Ñuñoa",
                    "sector": "Plaza Ñuñoa",
                    "address": "Av. Irarrázaval 0000, depto 000",
                    "sale_price_amount": 4800,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": 120000,
                    "property_tax_clp": 85000,
                    "bedrooms": 2,
                    "bathrooms": 2,
                    "usable_area_m2": 62,
                    "total_area_m2": 70,
                    "parking_spaces": 1,
                    "storage_units": 1,
                    "orientation": "norte",
                    "floor": 8,
                    "year_built": 2018,
                    "furnished": false,
                    "pets_allowed": true,
                    "photos": [
                      "https://example.com/i-12/1.jpg"
                    ],
                    "broker_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "owner_name": "Propietaria de ejemplo",
                    "owner_phone": "+56900000000",
                    "owner_email": "propietaria@example.com",
                    "source": "manual",
                    "external_ref": null,
                    "protected_fields": [],
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-14T12:00:00.000Z",
                    "updated_at": "2026-10-14T12:00:00.000Z",
                    "sale_price": {
                      "amount": 4800,
                      "currency": "UF",
                      "amount_clp": 189120000,
                      "amount_uf": 4800
                    },
                    "rent_price": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/properties/{id}": {
      "get": {
        "summary": "One property",
        "description": "`id` is the uuid or the display id (`I-12`). Includes the private fields (dirección, propietario) — this is the broker’s surface, not the agent’s. Requires `marketplace:read`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Property",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-12",
                    "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                    "description": "Luminoso, orientación norte, a pasos del metro.",
                    "operation": "venta",
                    "property_type": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "Ñuñoa",
                    "sector": "Plaza Ñuñoa",
                    "address": "Av. Irarrázaval 0000, depto 000",
                    "sale_price_amount": 4800,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": 120000,
                    "property_tax_clp": 85000,
                    "bedrooms": 2,
                    "bathrooms": 2,
                    "usable_area_m2": 62,
                    "total_area_m2": 70,
                    "parking_spaces": 1,
                    "storage_units": 1,
                    "orientation": "norte",
                    "floor": 8,
                    "year_built": 2018,
                    "furnished": false,
                    "pets_allowed": true,
                    "photos": [
                      "https://example.com/i-12/1.jpg"
                    ],
                    "broker_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "owner_name": "Propietaria de ejemplo",
                    "owner_phone": "+56900000000",
                    "owner_email": "propietaria@example.com",
                    "source": "manual",
                    "external_ref": null,
                    "protected_fields": [],
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-14T12:00:00.000Z",
                    "updated_at": "2026-10-14T12:00:00.000Z",
                    "sale_price": {
                      "amount": 4800,
                      "currency": "UF",
                      "amount_clp": 189120000,
                      "amount_uf": 4800
                    },
                    "rent_price": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a property from the cartera",
        "description": "Takes the property out of the cartera and out of every search, card and Instagram link. To take it off the market but keep it, pause it instead. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "display_id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "display_id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000001",
                    "display_id": "I-12",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a property (pause it with `status: pausada`)",
        "description": "Partial update. On a property that came from a portal or an import, every field this edit changes is protected: no later sync overwrites it until it is released (`protected_fields`, ADR 0023). Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePropertyBody"
              },
              "example": {
                "status": "pausada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-12",
                    "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                    "description": "Luminoso, orientación norte, a pasos del metro.",
                    "operation": "venta",
                    "property_type": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "Ñuñoa",
                    "sector": "Plaza Ñuñoa",
                    "address": "Av. Irarrázaval 0000, depto 000",
                    "sale_price_amount": 4800,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": 120000,
                    "property_tax_clp": 85000,
                    "bedrooms": 2,
                    "bathrooms": 2,
                    "usable_area_m2": 62,
                    "total_area_m2": 70,
                    "parking_spaces": 1,
                    "storage_units": 1,
                    "orientation": "norte",
                    "floor": 8,
                    "year_built": 2018,
                    "furnished": false,
                    "pets_allowed": true,
                    "photos": [
                      "https://example.com/i-12/1.jpg"
                    ],
                    "broker_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "owner_name": "Propietaria de ejemplo",
                    "owner_phone": "+56900000000",
                    "owner_email": "propietaria@example.com",
                    "source": "manual",
                    "external_ref": null,
                    "protected_fields": [],
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-14T12:00:00.000Z",
                    "updated_at": "2026-10-14T12:00:00.000Z",
                    "sale_price": {
                      "amount": 4800,
                      "currency": "UF",
                      "amount_clp": 189120000,
                      "amount_uf": 4800
                    },
                    "rent_price": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/properties/{id}/release-fields": {
      "post": {
        "summary": "Let protected fields follow the source again",
        "description": "Removes the named fields from `protected_fields`, so the next portal or import sync may overwrite them. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "minItems": 1,
                    "maxItems": 40,
                    "description": "Protected fields to hand back to the source (portal or import), so the next sync may overwrite them again."
                  }
                },
                "required": [
                  "fields"
                ]
              },
              "example": {
                "fields": [
                  "sale_price_amount"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Property"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-12",
                    "title": "Departamento 2D2B con terraza, metro Irarrázaval",
                    "description": "Luminoso, orientación norte, a pasos del metro.",
                    "operation": "venta",
                    "property_type": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "Ñuñoa",
                    "sector": "Plaza Ñuñoa",
                    "address": "Av. Irarrázaval 0000, depto 000",
                    "sale_price_amount": 4800,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": 120000,
                    "property_tax_clp": 85000,
                    "bedrooms": 2,
                    "bathrooms": 2,
                    "usable_area_m2": 62,
                    "total_area_m2": 70,
                    "parking_spaces": 1,
                    "storage_units": 1,
                    "orientation": "norte",
                    "floor": 8,
                    "year_built": 2018,
                    "furnished": false,
                    "pets_allowed": true,
                    "photos": [
                      "https://example.com/i-12/1.jpg"
                    ],
                    "broker_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "owner_name": "Propietaria de ejemplo",
                    "owner_phone": "+56900000000",
                    "owner_email": "propietaria@example.com",
                    "source": "manual",
                    "external_ref": null,
                    "protected_fields": [],
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-14T12:00:00.000Z",
                    "updated_at": "2026-10-14T12:00:00.000Z",
                    "sale_price": {
                      "amount": 4800,
                      "currency": "UF",
                      "amount_clp": 189120000,
                      "amount_uf": 4800
                    },
                    "rent_price": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/properties/{id}/publications": {
      "get": {
        "summary": "A property’s listings on the portals",
        "description": "One row per portal account the property is listed on, with the portal’s own state for the listing (`portal_state`: listed, paused, withdrawn, moderated). A listing paused or closed on the portal never changes the property’s `status`. Requires `marketplace:read`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Publications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PropertyPublication"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-0000000000dd",
                      "property_id": "0199e3a0-0000-7000-8000-000000000001",
                      "marketplace_account_id": "0199e3a0-0000-7000-8000-0000000000cc",
                      "provider": "mercadolibre",
                      "external_id": "MLC1500000001",
                      "status": "imported",
                      "origin": "portal",
                      "permalink": "https://www.portalinmobiliario.com/MLC-1500000001",
                      "listing_type": "silver",
                      "portal_state": "paused",
                      "portal_state_at": "2026-10-14T18:20:00.000Z",
                      "last_synced_at": "2026-10-15T09:00:00.000Z",
                      "last_published_at": null,
                      "last_error": null,
                      "created_at": "2026-10-14T12:00:00.000Z",
                      "updated_at": "2026-10-15T09:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Publish a property to Portal Inmobiliario",
        "description": "Lists the property on the account: the category from its tipo and operación, the price in UF (sent as CLF) or CLP, its attributes, comuna and dirección, up to 30 photos, the description, and the workspace WhatsApp line as the contact button. The tier comes from the packs the account holds (a publication pack is mandatory). Publishing again refreshes the live listing (`outcome: updated`). After that, the listing follows the property: edits are pushed, `pausada` pauses it, back on the market re-activates it, and `vendida` / `arrendada` or removing the property closes it (closed is final on the portal). Errors (422), each with `field_errors` keyed by the property field to fix: `portal_no_package` (no pack with cupos for the tier; `details.tiers` lists what the account has), `portal_listing_incomplete` (nothing was sent), `portal_listing_rejected` (the portal refused it; `details.causes` verbatim). 409 when the property already has a listing on this account that the import found on the portal. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The property uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The property uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The Portal Inmobiliario account to publish on."
                  },
                  "listing_type": {
                    "type": "string",
                    "enum": [
                      "silver",
                      "gold",
                      "gold_premium"
                    ],
                    "description": "Listing tier. Default silver (the publication pack). gold / gold_premium need a pack with cupos of that tier; see GET /properties/portals/{accountId}/listing-tiers."
                  },
                  "operation": {
                    "type": "string",
                    "enum": [
                      "venta",
                      "arriendo"
                    ],
                    "description": "Which offer to list. Required when the property is in venta y arriendo (one listing per account)."
                  }
                },
                "required": [
                  "account_id"
                ]
              },
              "example": {
                "account_id": "0199e3a0-0000-7000-8000-0000000000cc",
                "listing_type": "silver"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Published (or the live listing refreshed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublishPropertyResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "published",
                    "publication": {
                      "id": "0199e3a0-0000-7000-8000-0000000000dd",
                      "property_id": "0199e3a0-0000-7000-8000-000000000001",
                      "marketplace_account_id": "0199e3a0-0000-7000-8000-0000000000cc",
                      "provider": "mercadolibre",
                      "external_id": "MLC1500000001",
                      "status": "published",
                      "origin": "vitrina",
                      "permalink": "https://www.portalinmobiliario.com/MLC-1500000001",
                      "listing_type": "silver",
                      "portal_state": "listed",
                      "portal_state_at": "2026-10-14T18:20:00.000Z",
                      "last_synced_at": "2026-10-15T09:00:00.000Z",
                      "last_published_at": "2026-10-15T12:00:00.000Z",
                      "last_error": null,
                      "created_at": "2026-10-14T12:00:00.000Z",
                      "updated_at": "2026-10-15T09:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/properties/portals": {
      "get": {
        "summary": "Connected portal accounts and their import",
        "description": "The workspace’s Portal Inmobiliario (Mercado Libre) accounts: how many properties of the cartera each one lists, split by portal state, and the last import run with its counts. Requires `marketplace:read`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "responses": {
          "200": {
            "description": "Portal accounts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PropertyPortalAccount"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-0000000000cc",
                      "provider": "mercadolibre",
                      "portal": "portal_inmobiliario",
                      "name": "Mercado Libre – CORREDORA_DEMO",
                      "seller_nickname": "CORREDORA_DEMO",
                      "enabled": true,
                      "sync_enabled": true,
                      "connected_at": "2026-10-14T12:00:00.000Z",
                      "properties": {
                        "total": 6,
                        "listed": 5,
                        "paused": 1,
                        "other": 0
                      },
                      "last_run": {
                        "id": "0199e3a0-0000-7000-8000-0000000000ee",
                        "status": "success",
                        "started_at": "2026-10-15T09:00:00.000Z",
                        "finished_at": "2026-10-15T09:00:12.000Z",
                        "error_message": null,
                        "summary": {
                          "seen": 6,
                          "created": 0,
                          "updated": 1,
                          "unchanged": 5,
                          "skipped": 0,
                          "not_real_estate": 0,
                          "removed_by_broker": 0,
                          "confirmed_absent": 0,
                          "portal_states": {
                            "listed": 5,
                            "paused": 1
                          },
                          "truncated": false
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/properties/portals/mercadolibre/connect": {
      "post": {
        "summary": "Start connecting Portal Inmobiliario",
        "description": "Returns Mercado Libre’s consent URL (Portal Inmobiliario is Mercado Libre’s real-estate site; the broker signs in with their Mercado Libre account). After consent the browser returns to `return_to` with `status=connected`, and the first import runs at the next scheduler tick (≤ 15 minutes), or right away with `POST /properties/portals/{accountId}/sync`. 503 `MERCADOLIBRE_NOT_CONFIGURED` when the platform has no Mercado Libre app. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "return_to": {
                    "type": "string",
                    "maxLength": 2000,
                    "format": "uri",
                    "description": "Where the browser lands after Mercado Libre’s consent."
                  }
                },
                "default": {}
              },
              "example": {
                "return_to": "https://example.com/vuelta-del-consentimiento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Consent URL",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "authorize_url": {
                          "type": "string",
                          "format": "uri"
                        }
                      },
                      "required": [
                        "authorize_url"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "authorize_url": "https://auth.mercadolibre.cl/authorization?response_type=code&client_id=…"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/properties/portals/{accountId}/sync": {
      "post": {
        "summary": "Import the account’s listings now",
        "description": "Queues an import of every active and paused listing of the account into the cartera (also runs every 6 hours). Re-running an unchanged account changes nothing; a field the broker edited in Vitrina is never overwritten; the property’s status is never written. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "enqueued": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "enqueued"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enqueued": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/properties/portals/{accountId}": {
      "delete": {
        "summary": "Disconnect a portal account",
        "description": "Removes the connection and its listing records. The properties it imported stay in the cartera. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Disconnected"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/properties/{id}/publications/{publicationId}": {
      "delete": {
        "summary": "Close a listing on the portal",
        "description": "Closes the listing on Portal Inmobiliario (final there: publishing again creates a new listing). The property and its status are untouched. Requires `marketplace:write`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "publicationId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PropertyPublication"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-0000000000dd",
                    "property_id": "0199e3a0-0000-7000-8000-000000000001",
                    "marketplace_account_id": "0199e3a0-0000-7000-8000-0000000000cc",
                    "provider": "mercadolibre",
                    "external_id": "MLC1500000001",
                    "status": "deleted",
                    "origin": "vitrina",
                    "permalink": "https://www.portalinmobiliario.com/MLC-1500000001",
                    "listing_type": "silver",
                    "portal_state": "withdrawn",
                    "portal_state_at": "2026-10-14T18:20:00.000Z",
                    "last_synced_at": "2026-10-15T09:00:00.000Z",
                    "last_published_at": "2026-10-15T12:00:00.000Z",
                    "last_error": null,
                    "created_at": "2026-10-14T12:00:00.000Z",
                    "updated_at": "2026-10-15T09:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/properties/portals/{accountId}/listing-tiers": {
      "get": {
        "summary": "Listing tiers the account’s packs allow",
        "description": "The tiers the account can publish with right now, read from its active Mercado Libre packs, with cupos left. Empty `data` = no pack: publishing answers `portal_no_package`. Requires `marketplace:read`. Only on a workspace whose vertical is `real-estate`; any other answers 403 `VERTICAL_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Properties"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "accountId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tiers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PropertyListingTier"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "listing_type": "silver",
                      "via": "publication",
                      "remaining": 37
                    },
                    {
                      "listing_type": "gold",
                      "via": "upgrade",
                      "remaining": 5
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects": {
      "get": {
        "summary": "List proyectos",
        "description": "Live proyectos, newest first, every filter ANDed: `comuna` (accent- and case-insensitive), `stage` (etapa), `subsidy` (qualifies for any), `sales_office_location_id`, free text `q`. Each row carries `unit_model_count` and the stock summary `units_total`, `units_available` and `price_from_uf` (the «desde» in UF) and `member_count` (the team). Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over name, comuna and address."
            },
            "required": false,
            "description": "Free text over name, comuna and address.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "maxItems": 50,
              "description": "Comma-separated, accent- and case-insensitive."
            },
            "required": false,
            "description": "Comma-separated, accent- and case-insensitive.",
            "name": "comuna",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "en_blanco",
                  "en_verde",
                  "entrega_inmediata"
                ],
                "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
              },
              "maxItems": 50,
              "description": "Comma-separated etapas."
            },
            "required": false,
            "description": "Comma-separated etapas.",
            "name": "stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "DS19",
                  "DS1",
                  "DS49"
                ]
              },
              "maxItems": 50,
              "description": "Proyectos that qualify for ANY of these (comma-separated)."
            },
            "required": false,
            "description": "Proyectos that qualify for ANY of these (comma-separated).",
            "name": "subsidy",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only the proyectos this sala de ventas serves."
            },
            "required": false,
            "description": "Only the proyectos this sala de ventas serves.",
            "name": "sales_office_location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Proyectos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectListItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "name": "Parque Los Aromos",
                      "description": "Condominio de departamentos a pasos del metro.",
                      "region": "Metropolitana",
                      "comuna": "La Florida",
                      "address": "Av. Vicuña Mackenna 0000",
                      "stage": "en_verde",
                      "delivery_date": "2027-12-01",
                      "recepcion_definitiva_at": null,
                      "on_call_user_ids": [],
                      "subsidies": [
                        "DS19"
                      ],
                      "sales_office_location_id": "0199e3a0-0000-7000-8000-000000000301",
                      "photos": [
                        "https://media.example.com/t/other/aromos-1.jpg"
                      ],
                      "brochure_url": "https://media.example.com/t/other/aromos-brochure.pdf",
                      "meta_ad_ids": [
                        "120211111111111111"
                      ],
                      "whatsapp_code": "RK482",
                      "visor_show_prices": false,
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "unit_model_count": 1,
                      "units_total": 40,
                      "units_available": 12,
                      "price_from_uf": 3250.5,
                      "member_count": 2
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a proyecto",
        "description": "Names are unique among the workspace’s live proyectos (409 otherwise). The sala de ventas is an active location of the workspace (`GET /locations`); one sala may serve several proyectos. Media fields (`photos`, `brochure_url`) are public URLs: upload the file with `POST /tenant/media` first and send its `url`. Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectBody"
              },
              "example": {
                "name": "Parque Los Aromos",
                "comuna": "La Florida",
                "region": "Metropolitana",
                "stage": "en_verde",
                "delivery_date": "2027-12-01",
                "subsidies": [
                  "DS19"
                ],
                "sales_office_location_id": "0199e3a0-0000-7000-8000-000000000301"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000101",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "name": "Parque Los Aromos",
                    "description": "Condominio de departamentos a pasos del metro.",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "address": "Av. Vicuña Mackenna 0000",
                    "stage": "en_verde",
                    "delivery_date": "2027-12-01",
                    "recepcion_definitiva_at": null,
                    "on_call_user_ids": [],
                    "subsidies": [
                      "DS19"
                    ],
                    "sales_office_location_id": "0199e3a0-0000-7000-8000-000000000301",
                    "photos": [
                      "https://media.example.com/t/other/aromos-1.jpg"
                    ],
                    "brochure_url": "https://media.example.com/t/other/aromos-brochure.pdf",
                    "meta_ad_ids": [
                      "120211111111111111"
                    ],
                    "whatsapp_code": "RK482",
                    "visor_show_prices": false,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sales_office": {
                      "id": "0199e3a0-0000-7000-8000-000000000301",
                      "display_id": "B-2",
                      "name": "Sala de ventas La Florida"
                    },
                    "unit_models": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000201",
                        "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                        "project_id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "3D2B 78 m²",
                        "description": "Esquina, con terraza en L.",
                        "bedrooms": 3,
                        "bathrooms": 2,
                        "usable_area_m2": 72.5,
                        "terrace_area_m2": 6,
                        "total_area_m2": 78.5,
                        "floor_plans": [
                          "https://media.example.com/t/other/planta-3d2b.pdf"
                        ],
                        "renders": [
                          "https://media.example.com/t/other/render-3d2b.jpg"
                        ],
                        "tour_360_url": "https://tour.example.com/3d2b",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "updated_at": "2026-10-20T12:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/projects/{id}": {
      "get": {
        "summary": "One proyecto, with its sala de ventas and tipologías",
        "description": "Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Proyecto",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000101",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "name": "Parque Los Aromos",
                    "description": "Condominio de departamentos a pasos del metro.",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "address": "Av. Vicuña Mackenna 0000",
                    "stage": "en_verde",
                    "delivery_date": "2027-12-01",
                    "recepcion_definitiva_at": null,
                    "on_call_user_ids": [],
                    "subsidies": [
                      "DS19"
                    ],
                    "sales_office_location_id": "0199e3a0-0000-7000-8000-000000000301",
                    "photos": [
                      "https://media.example.com/t/other/aromos-1.jpg"
                    ],
                    "brochure_url": "https://media.example.com/t/other/aromos-brochure.pdf",
                    "meta_ad_ids": [
                      "120211111111111111"
                    ],
                    "whatsapp_code": "RK482",
                    "visor_show_prices": false,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sales_office": {
                      "id": "0199e3a0-0000-7000-8000-000000000301",
                      "display_id": "B-2",
                      "name": "Sala de ventas La Florida"
                    },
                    "unit_models": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000201",
                        "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                        "project_id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "3D2B 78 m²",
                        "description": "Esquina, con terraza en L.",
                        "bedrooms": 3,
                        "bathrooms": 2,
                        "usable_area_m2": 72.5,
                        "terrace_area_m2": 6,
                        "total_area_m2": 78.5,
                        "floor_plans": [
                          "https://media.example.com/t/other/planta-3d2b.pdf"
                        ],
                        "renders": [
                          "https://media.example.com/t/other/render-3d2b.jpg"
                        ],
                        "tour_360_url": "https://tour.example.com/3d2b",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "updated_at": "2026-10-20T12:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a proyecto",
        "description": "Takes the proyecto and its tipologías out of every list. Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "name",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "Parque Los Aromos",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a proyecto",
        "description": "Partial update; `null` clears a field. Media fields (`photos`, `brochure_url`) are public URLs: upload the file with `POST /tenant/media` first and send its `url`. Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectBody"
              },
              "example": {
                "stage": "entrega_inmediata"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Project"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000101",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "name": "Parque Los Aromos",
                    "description": "Condominio de departamentos a pasos del metro.",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "address": "Av. Vicuña Mackenna 0000",
                    "stage": "en_verde",
                    "delivery_date": "2027-12-01",
                    "recepcion_definitiva_at": null,
                    "on_call_user_ids": [],
                    "subsidies": [
                      "DS19"
                    ],
                    "sales_office_location_id": "0199e3a0-0000-7000-8000-000000000301",
                    "photos": [
                      "https://media.example.com/t/other/aromos-1.jpg"
                    ],
                    "brochure_url": "https://media.example.com/t/other/aromos-brochure.pdf",
                    "meta_ad_ids": [
                      "120211111111111111"
                    ],
                    "whatsapp_code": "RK482",
                    "visor_show_prices": false,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sales_office": {
                      "id": "0199e3a0-0000-7000-8000-000000000301",
                      "display_id": "B-2",
                      "name": "Sala de ventas La Florida"
                    },
                    "unit_models": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000201",
                        "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                        "project_id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "3D2B 78 m²",
                        "description": "Esquina, con terraza en L.",
                        "bedrooms": 3,
                        "bathrooms": 2,
                        "usable_area_m2": 72.5,
                        "terrace_area_m2": 6,
                        "total_area_m2": 78.5,
                        "floor_plans": [
                          "https://media.example.com/t/other/planta-3d2b.pdf"
                        ],
                        "renders": [
                          "https://media.example.com/t/other/render-3d2b.jpg"
                        ],
                        "tour_360_url": "https://tour.example.com/3d2b",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "updated_at": "2026-10-20T12:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/unit-models": {
      "get": {
        "summary": "List tipologías",
        "description": "Live tipologías of live proyectos, grouped by proyecto (newest first) then by name. Filters ANDed: `project_id`, the proyecto’s `comuna` and `stage` (etapa), `min_bedrooms`, `min_bathrooms`, free text `q`. Each row names its proyecto. Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos (comma-separated ids)."
            },
            "required": false,
            "description": "Only these proyectos (comma-separated ids).",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "minLength": 1,
                "maxLength": 120
              },
              "maxItems": 50,
              "description": "The proyecto’s comuna. Comma-separated, accent- and case-insensitive."
            },
            "required": false,
            "description": "The proyecto’s comuna. Comma-separated, accent- and case-insensitive.",
            "name": "comuna",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "en_blanco",
                  "en_verde",
                  "entrega_inmediata"
                ],
                "description": "Etapa: en_blanco (before construction) | en_verde (under construction) | entrega_inmediata (built, ready to hand over)."
              },
              "maxItems": 50,
              "description": "The proyecto’s etapa (comma-separated)."
            },
            "required": false,
            "description": "The proyecto’s etapa (comma-separated).",
            "name": "stage",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_bedrooms",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_bathrooms",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over the tipología’s and its proyecto’s name."
            },
            "required": false,
            "description": "Free text over the tipología’s and its proyecto’s name.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tipologías",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UnitModelListItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000201",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "3D2B 78 m²",
                      "description": "Esquina, con terraza en L.",
                      "bedrooms": 3,
                      "bathrooms": 2,
                      "usable_area_m2": 72.5,
                      "terrace_area_m2": 6,
                      "total_area_m2": 78.5,
                      "floor_plans": [
                        "https://media.example.com/t/other/planta-3d2b.pdf"
                      ],
                      "renders": [
                        "https://media.example.com/t/other/render-3d2b.jpg"
                      ],
                      "tour_360_url": "https://tour.example.com/3d2b",
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "project": {
                        "id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "Parque Los Aromos",
                        "comuna": "La Florida",
                        "stage": "en_verde"
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a tipología to a proyecto",
        "description": "Names are unique among the proyecto’s live tipologías (409 otherwise). `floor_plans` (plantas), `renders` and `tour_360_url` are public URLs: upload files with `POST /tenant/media` first. Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUnitModelBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "name": "3D2B 78 m²",
                "bedrooms": 3,
                "bathrooms": 2,
                "usable_area_m2": 72.5,
                "terrace_area_m2": 6,
                "total_area_m2": 78.5
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitModel"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000201",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "3D2B 78 m²",
                    "description": "Esquina, con terraza en L.",
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "floor_plans": [
                      "https://media.example.com/t/other/planta-3d2b.pdf"
                    ],
                    "renders": [
                      "https://media.example.com/t/other/render-3d2b.jpg"
                    ],
                    "tour_360_url": "https://tour.example.com/3d2b",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/unit-models/{id}": {
      "get": {
        "summary": "One tipología",
        "description": "Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The tipología id."
            },
            "required": true,
            "description": "The tipología id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tipología",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitModel"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000201",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "3D2B 78 m²",
                    "description": "Esquina, con terraza en L.",
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "floor_plans": [
                      "https://media.example.com/t/other/planta-3d2b.pdf"
                    ],
                    "renders": [
                      "https://media.example.com/t/other/render-3d2b.jpg"
                    ],
                    "tour_360_url": "https://tour.example.com/3d2b",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a tipología",
        "description": "Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The tipología id."
            },
            "required": true,
            "description": "The tipología id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "name": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "project_id",
                        "name",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000201",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "3D2B 78 m²",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a tipología",
        "description": "Partial update; `null` clears a field. A tipología stays in its proyecto. Requires `marketplace:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The tipología id."
            },
            "required": true,
            "description": "The tipología id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUnitModelBody"
              },
              "example": {
                "bedrooms": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitModel"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000201",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "3D2B 78 m²",
                    "description": "Esquina, con terraza en L.",
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "floor_plans": [
                      "https://media.example.com/t/other/planta-3d2b.pdf"
                    ],
                    "renders": [
                      "https://media.example.com/t/other/render-3d2b.jpg"
                    ],
                    "tour_360_url": "https://tour.example.com/3d2b",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/whatsapp-links": {
      "get": {
        "summary": "Get the tracked WhatsApp links of a proyecto",
        "description": "One `wa.me` link per enabled WhatsApp number of the workspace, each prefilling a message that carries the proyecto’s `whatsapp_code`. A buyer who opens it and sends that message starts a conversation tied to the proyecto: the agent opens on it by name, and the lead is about it (`lead.project_id`, owned by the proyecto’s ejecutivos) with the link in its notes. Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Tracked links",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProjectWhatsappLinks"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "whatsapp_code": "RK482",
                    "prefill_text": "Hola, me interesa el proyecto Parque Los Aromos (código RK482)",
                    "links": [
                      {
                        "messaging_account_id": "0199e3a0-0000-7000-8000-000000000501",
                        "name": "WhatsApp ventas",
                        "phone_e164": "+56912345678",
                        "url": "https://wa.me/56912345678?text=Hola%2C%20me%20interesa%20el%20proyecto%20Parque%20Los%20Aromos%20(c%C3%B3digo%20RK482)"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/members": {
      "get": {
        "summary": "List the ejecutivos of a proyecto",
        "description": "The members assigned to this proyecto, in the order they joined it. An ejecutivo on a role with `project_visibility: assigned` sees only the leads of the proyectos they are assigned to, and a new lead about the proyecto (`lead.project_id`) with no owner is owned by one of them, round-robin, through the assignment chain. Requires `marketplace:read`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Ejecutivos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "user_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "project_id",
                          "user_id",
                          "created_by",
                          "created_at"
                        ],
                        "description": "An ejecutivo de ventas assigned to a proyecto: they see its leads, and a new lead about it is owned by one of them (round-robin)."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "user_id": "0199e3a0-0000-7000-8000-000000000401",
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Assign an ejecutivo to a proyecto",
        "description": "Assigns an ACTIVE member of this workspace (400 otherwise). Idempotent: assigning someone already on the team returns their row. The member sees the proyecto’s leads on their next request and joins its lead rotation. Requires `memberships:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "An ACTIVE member of this workspace. Assigning is idempotent."
                  }
                },
                "required": [
                  "user_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "user_id": "0199e3a0-0000-7000-8000-000000000401"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Assigned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "user_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "project_id",
                        "user_id",
                        "created_by",
                        "created_at"
                      ],
                      "description": "An ejecutivo de ventas assigned to a proyecto: they see its leads, and a new lead about it is owned by one of them (round-robin)."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "user_id": "0199e3a0-0000-7000-8000-000000000401",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/members/{userId}": {
      "delete": {
        "summary": "Unassign an ejecutivo from a proyecto",
        "description": "Takes the member off the proyecto’s team: they stop seeing its leads (except the ones they own) and leave its rotation. 404 when they were not on it. Requires `memberships:write`. A caller whose role narrows proyectos (`project_visibility` `assigned` = ejecutivo, `sales_office` = jefe de sala) only sees the proyectos it admits; any other answers 404, as if it did not exist. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The ejecutivo (a member of this workspace)."
            },
            "required": true,
            "description": "The ejecutivo (a member of this workspace).",
            "name": "userId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Unassigned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "user_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "project_id",
                        "user_id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "user_id": "0199e3a0-0000-7000-8000-000000000401",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/project-listings": {
      "get": {
        "summary": "List portal anuncios (Portal Inmobiliario, Yapo) and their proyecto",
        "description": "Anuncios (portal listings) of the workspace with how many leads arrived on each. `mapped=false` is the queue of anuncios waiting for a proyecto. Requires `marketplace:read`. A question or contact on a Portal Inmobiliario proyecto, or on a Yapo real-estate ad (matched by its Yapo ad id, `adid`), becomes a lead on the proyecto its anuncio is mapped to (`lead.project_id`), owned by one of its ejecutivos. An anuncio nobody has mapped yet is recorded on its first inquiry and that lead is flagged for mapping (`project_id` null, note on the lead). Only on the Proyectos module; callers whose role narrows proyectos are refused (403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`false` = the anuncios still waiting for a proyecto (their leads are flagged for mapping); `true` = the mapped ones."
            },
            "required": false,
            "description": "`false` = the anuncios still waiting for a proyecto (their leads are flagged for mapping); `true` = the mapped ones.",
            "name": "mapped",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only the anuncios of this proyecto."
            },
            "required": false,
            "description": "Only the anuncios of this proyecto.",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Anuncios",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectListingListItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                      "external_id": "MLC1234567890",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "title": "Proyecto Parque Central, Ñuñoa",
                      "permalink": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "provider": "mercadolibre",
                      "lead_count": 4,
                      "unmapped_lead_count": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Map a Portal Inmobiliario anuncio to a proyecto",
        "description": "Maps the portal's listing id to a proyecto ahead of its first inquiry. Idempotent on the anuncio: posting an already-seen id re-maps it, stamps the proyecto on its waiting leads and routes the unowned ones to the proyecto's ejecutivos. Requires `marketplace:write`. A question or contact on a Portal Inmobiliario proyecto, or on a Yapo real-estate ad (matched by its Yapo ad id, `adid`), becomes a lead on the proyecto its anuncio is mapped to (`lead.project_id`), owned by one of its ejecutivos. An anuncio nobody has mapped yet is recorded on its first inquiry and that lead is flagged for mapping (`project_id` null, note on the lead). Only on the Proyectos module; callers whose role narrows proyectos are refused (403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateProjectListingBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "external_id": "MLC1234567890"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Mapped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MappedProjectListing"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                    "external_id": "MLC1234567890",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "title": "Proyecto Parque Central, Ñuñoa",
                    "permalink": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "leads_mapped": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/project-listings/{id}": {
      "patch": {
        "summary": "Map or unmap a Portal Inmobiliario anuncio",
        "description": "Sets the proyecto of an anuncio (`project_id`), or clears it with `null`. Mapping stamps the proyecto on the leads waiting on the anuncio and routes the unowned ones to its ejecutivos; unmapping only affects leads that arrive afterwards. Requires `marketplace:write`. A question or contact on a Portal Inmobiliario proyecto, or on a Yapo real-estate ad (matched by its Yapo ad id, `adid`), becomes a lead on the proyecto its anuncio is mapped to (`lead.project_id`), owned by one of its ejecutivos. An anuncio nobody has mapped yet is recorded on its first inquiry and that lead is flagged for mapping (`project_id` null, note on the lead). Only on the Proyectos module; callers whose role narrows proyectos are refused (403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The anuncio (portal listing) id."
            },
            "required": true,
            "description": "The anuncio (portal listing) id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateProjectListingBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MappedProjectListing"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                    "external_id": "MLC1234567890",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "title": "Proyecto Parque Central, Ñuñoa",
                    "permalink": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "leads_mapped": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/publication-readiness": {
      "get": {
        "summary": "Check a proyecto before publishing it to Portal Inmobiliario",
        "description": "Dry run: what the portal would refuse, with nothing sent. `field_errors` is keyed by the field to fix (a proyecto field, `models.<tipología>.<field>`, `price`, `comuna`, `photos`, `seller_contact`). A tipología needs its dormitorios, baños, superficies (útil, terraza, total) and a price in the published lista; its orientation, floor and estacionamientos come from its unidades, and gastos comunes from the proyecto’s unidades. Requires `marketplace:read`. The proyecto is ONE listing in the portal’s «Proyectos» category and each tipología a variation of it, with the «desde» price (UF, from the published lista de precios) and the stock (disponible unidades) of that tipología. The listing is the anuncio that maps inbound leads to the proyecto, so its leads land on the proyecto with no mapping step. Only on the Proyectos module.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The Portal Inmobiliario account to check."
            },
            "required": true,
            "description": "The Portal Inmobiliario account to check.",
            "name": "account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Readiness",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProjectPublishReadiness"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ready": false,
                    "field_errors": {
                      "models.3D2B.orientation": "Falta «Orientación (de sus unidades)» en la tipología «3D2B»."
                    },
                    "models_listed": 3,
                    "units_available": 41,
                    "price_from_uf": 2890
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/publications": {
      "get": {
        "summary": "List a proyecto’s Portal Inmobiliario anuncios",
        "description": "The anuncios of the proyecto with the portal’s state (`portal_state`), the last push and its error. Requires `marketplace:read`. The proyecto is ONE listing in the portal’s «Proyectos» category and each tipología a variation of it, with the «desde» price (UF, from the published lista de precios) and the stock (disponible unidades) of that tipología. The listing is the anuncio that maps inbound leads to the proyecto, so its leads land on the proyecto with no mapping step. Only on the Proyectos module.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Anuncios",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectPublication"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "provider": "mercadolibre",
                      "external_id": "MLC1234567890",
                      "title": "Parque Central",
                      "permalink": "https://departamento.mercadolibre.cl/MLC-1234567890-parque-central-_JM",
                      "origin": "vitrina",
                      "status": "published",
                      "listing_type": "gold_premium",
                      "portal_state": "listed",
                      "portal_state_at": "2026-10-20T12:00:00.000Z",
                      "paused_by_vitrina": false,
                      "tipologias_listed": 1,
                      "last_published_at": "2026-10-20T12:00:00.000Z",
                      "last_synced_at": "2026-10-20T12:00:00.000Z",
                      "last_error": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Publish a proyecto and its tipologías to Portal Inmobiliario",
        "description": "Lists the proyecto, or refreshes its live listing (`outcome: updated`). After that the listing follows the proyecto: an edit of the proyecto, a tipología or a unidad, and a new published lista de precios, push the content (price «desde» and stock per tipología); when no unidad is disponible the listing is paused, and resumed when stock returns; removing the proyecto closes it (final on the portal). A pause or close on the portal is recorded as `portal_state` and never changes a unidad’s status. Errors (422), each with `field_errors`: `portal_listing_incomplete` (nothing was sent), `portal_no_package` (the account has no developments pack with cupo, or lacks the developments permission: each pack allows one publication), `portal_listing_rejected` (the portal refused it; `details.causes` verbatim). Requires `marketplace:write`. The proyecto is ONE listing in the portal’s «Proyectos» category and each tipología a variation of it, with the «desde» price (UF, from the published lista de precios) and the stock (disponible unidades) of that tipología. The listing is the anuncio that maps inbound leads to the proyecto, so its leads land on the proyecto with no mapping step. Only on the Proyectos module.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublishProjectBody"
              },
              "example": {
                "account_id": "0199e3a0-0000-7000-8000-000000000601"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Published (or the live listing refreshed)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PublishProjectResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "published",
                    "publication": {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "provider": "mercadolibre",
                      "external_id": "MLC1234567890",
                      "title": "Parque Central",
                      "permalink": "https://departamento.mercadolibre.cl/MLC-1234567890-parque-central-_JM",
                      "origin": "vitrina",
                      "status": "published",
                      "listing_type": "gold_premium",
                      "portal_state": "listed",
                      "portal_state_at": "2026-10-20T12:00:00.000Z",
                      "paused_by_vitrina": false,
                      "tipologias_listed": 1,
                      "last_published_at": "2026-10-20T12:00:00.000Z",
                      "last_synced_at": "2026-10-20T12:00:00.000Z",
                      "last_error": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/publications/{publicationId}": {
      "delete": {
        "summary": "Close a proyecto’s listing on Portal Inmobiliario",
        "description": "Closes the listing on the portal (final there: publishing again creates a new one). The proyecto and its unidades are untouched. Requires `marketplace:write`. The proyecto is ONE listing in the portal’s «Proyectos» category and each tipología a variation of it, with the «desde» price (UF, from the published lista de precios) and the stock (disponible unidades) of that tipología. The listing is the anuncio that maps inbound leads to the proyecto, so its leads land on the proyecto with no mapping step. Only on the Proyectos module.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "publicationId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Closed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ProjectPublication"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "marketplace_account_id": "0199e3a0-0000-7000-8000-000000000601",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "provider": "mercadolibre",
                    "external_id": "MLC1234567890",
                    "title": "Parque Central",
                    "permalink": "https://departamento.mercadolibre.cl/MLC-1234567890-parque-central-_JM",
                    "origin": "vitrina",
                    "status": "deleted",
                    "listing_type": "gold_premium",
                    "portal_state": "withdrawn",
                    "portal_state_at": "2026-10-20T12:00:00.000Z",
                    "paused_by_vitrina": false,
                    "tipologias_listed": 1,
                    "last_published_at": "2026-10-20T12:00:00.000Z",
                    "last_synced_at": "2026-10-20T12:00:00.000Z",
                    "last_error": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/channel-partners": {
      "get": {
        "summary": "List corredores externos and referidores",
        "description": "The inmobiliaria's channel: brokers outside the company and referrers, each with the personal code of their link and the commission agreed. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "broker",
                "referrer"
              ],
              "description": "`broker` = corredor externo (a broker outside the inmobiliaria); `referrer` = referidor (often an owner who refers a buyer)."
            },
            "required": false,
            "description": "`broker` = corredor externo (a broker outside the inmobiliaria); `referrer` = referidor (often an owner who refers a buyer).",
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused"
              ],
              "description": "A paused partner cannot register or be credited new buyers."
            },
            "required": false,
            "description": "A paused partner cannot register or be credited new buyers.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Name, company, email or code."
            },
            "required": false,
            "description": "Name, company, email or code.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Partners",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ChannelPartnerListItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000701",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "kind": "broker",
                      "name": "María López",
                      "company": "López Propiedades",
                      "email": "maria@lopezpropiedades.example",
                      "phone": "+56912345678",
                      "code": "marialopez4k2x",
                      "referral_param": "ref=marialopez4k2x",
                      "status": "active",
                      "protection_days": 90,
                      "commission_basis": "percent",
                      "commission_value": 2,
                      "commission_currency": null,
                      "commission_milestone": "escritura",
                      "notes": null,
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "active_claim_count": 3,
                      "due_commission_count": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a corredor externo or referidor",
        "description": "Creates the partner and their personal code. Append `ref=<code>` to a proyecto page or form URL: a buyer who submits the proyecto form through it is credited to the partner and protected for `protection_days` (a second partner claiming the same buyer on the same proyecto inside the window is refused). The commission falls due when the negocio reaches `commission_milestone`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateChannelPartnerBody"
              },
              "example": {
                "kind": "broker",
                "name": "María López",
                "company": "López Propiedades",
                "protection_days": 90,
                "commission_basis": "percent",
                "commission_value": 2,
                "commission_milestone": "escritura"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChannelPartner"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000701",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "kind": "broker",
                    "name": "María López",
                    "company": "López Propiedades",
                    "email": "maria@lopezpropiedades.example",
                    "phone": "+56912345678",
                    "code": "marialopez4k2x",
                    "referral_param": "ref=marialopez4k2x",
                    "status": "active",
                    "protection_days": 90,
                    "commission_basis": "percent",
                    "commission_value": 2,
                    "commission_currency": null,
                    "commission_milestone": "escritura",
                    "notes": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/channel-partners/{id}": {
      "get": {
        "summary": "One corredor externo or referidor",
        "description": "Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The corredor or referidor id."
            },
            "required": true,
            "description": "The corredor or referidor id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The partner",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChannelPartner"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000701",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "kind": "broker",
                    "name": "María López",
                    "company": "López Propiedades",
                    "email": "maria@lopezpropiedades.example",
                    "phone": "+56912345678",
                    "code": "marialopez4k2x",
                    "referral_param": "ref=marialopez4k2x",
                    "status": "active",
                    "protection_days": 90,
                    "commission_basis": "percent",
                    "commission_value": 2,
                    "commission_currency": null,
                    "commission_milestone": "escritura",
                    "notes": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a corredor externo or referidor",
        "description": "Changes their data, protection window or commission terms, or pauses them (`status`). New terms apply to buyers credited from now on: the commission of a buyer already credited keeps the terms it was promised under. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The corredor or referidor id."
            },
            "required": true,
            "description": "The corredor or referidor id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateChannelPartnerBody"
              },
              "example": {
                "protection_days": 120
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChannelPartner"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000701",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "kind": "broker",
                    "name": "María López",
                    "company": "López Propiedades",
                    "email": "maria@lopezpropiedades.example",
                    "phone": "+56912345678",
                    "code": "marialopez4k2x",
                    "referral_param": "ref=marialopez4k2x",
                    "status": "active",
                    "protection_days": 90,
                    "commission_basis": "percent",
                    "commission_value": 2,
                    "commission_currency": null,
                    "commission_milestone": "escritura",
                    "notes": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/channel-partners/{id}/claims": {
      "get": {
        "summary": "The buyers a partner brought",
        "description": "Their claims, newest first, including the refused ones (a claim lost to an earlier one still inside its window). Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The corredor or referidor id."
            },
            "required": true,
            "description": "The corredor or referidor id.",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "refused"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Claims",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ChannelClaim"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000702",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000705",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "status": "active",
                      "claimed_at": "2026-10-20T12:00:00.000Z",
                      "protected_until": "2027-01-18T12:00:00.000Z",
                      "refused_by_claim_id": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Credit a lead to a corredor or referidor",
        "description": "Registers by hand that the lead's buyer was brought by the partner (a buyer who reached WhatsApp instead of the form). The buyer is protected for the partner's window. The same partner claiming again inside their window changes nothing (200). A different partner claiming the same buyer on the same proyecto inside someone's window is refused with 409 `CONFLICT` whose `details.code` is `LEAD_PROTECTED` and carries `protected_until` and `protected_by_partner_id`. A paused partner cannot claim (400). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The corredor or referidor id."
            },
            "required": true,
            "description": "The corredor or referidor id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClaimChannelLeadBody"
              },
              "example": {
                "lead_id": "0199e3a0-0000-7000-8000-000000000704"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same partner already holds the buyer",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChannelClaimResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "already_credited",
                    "claim": {
                      "id": "0199e3a0-0000-7000-8000-000000000702",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000705",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "status": "active",
                      "claimed_at": "2026-10-20T12:00:00.000Z",
                      "protected_until": "2027-01-18T12:00:00.000Z",
                      "refused_by_claim_id": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "commission": {
                      "id": "0199e3a0-0000-7000-8000-000000000703",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "basis": "percent",
                      "rate_value": 2,
                      "currency": null,
                      "milestone": "escritura",
                      "status": "pending",
                      "base_amount": null,
                      "amount": null,
                      "due_at": null,
                      "paid_at": null,
                      "paid_reference": null,
                      "cancelled_reason": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "201": {
            "description": "Credited",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChannelClaimResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "outcome": "credited",
                    "claim": {
                      "id": "0199e3a0-0000-7000-8000-000000000702",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000705",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "status": "active",
                      "claimed_at": "2026-10-20T12:00:00.000Z",
                      "protected_until": "2027-01-18T12:00:00.000Z",
                      "refused_by_claim_id": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "commission": {
                      "id": "0199e3a0-0000-7000-8000-000000000703",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "basis": "percent",
                      "rate_value": 2,
                      "currency": null,
                      "milestone": "escritura",
                      "status": "pending",
                      "base_amount": null,
                      "amount": null,
                      "due_at": null,
                      "paid_at": null,
                      "paid_reference": null,
                      "cancelled_reason": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/channel-commissions": {
      "get": {
        "summary": "List channel commissions",
        "description": "Commissions and rewards owed to corredores and referidores, from pending to paid. `status=due` is the payout queue. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "partner_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "due",
                "paid",
                "cancelled"
              ],
              "description": "`pending` = the milestone is not reached; `due` = reached, to be paid; `paid`; `cancelled` = desistimiento."
            },
            "required": false,
            "description": "`pending` = the milestone is not reached; `due` = reached, to be paid; `paid`; `cancelled` = desistimiento.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Commissions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/ChannelCommission"
                          },
                          {
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000703",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "basis": "percent",
                      "rate_value": 2,
                      "currency": null,
                      "milestone": "escritura",
                      "status": "pending",
                      "base_amount": null,
                      "amount": null,
                      "due_at": null,
                      "paid_at": null,
                      "paid_reference": null,
                      "cancelled_reason": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/channel-commissions/milestones": {
      "post": {
        "summary": "Record that a lead’s negocio reached a milestone",
        "description": "Every open commission of the lead whose agreed milestone is this one (or an earlier one) falls due. A percentage is computed on `base_amount` (default: the lead's value); due with no value yet, it takes its amount when the milestone is recorded again with one. Idempotent. The negocio flow records its milestones itself; this route is the manual door. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecordChannelMilestoneBody"
              },
              "example": {
                "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                "milestone": "escritura",
                "base_amount": 3500,
                "base_currency": "UF"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The commissions that fell due",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/ChannelCommission"
                          },
                          {
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000703",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                      "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "basis": "percent",
                      "rate_value": 2,
                      "currency": "UF",
                      "milestone": "escritura",
                      "status": "due",
                      "base_amount": 3500,
                      "amount": 70,
                      "due_at": "2026-12-01T12:00:00.000Z",
                      "paid_at": null,
                      "paid_reference": null,
                      "cancelled_reason": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/channel-commissions/{id}/pay": {
      "post": {
        "summary": "Mark a commission paid",
        "description": "Closes the payout of a due commission with the transfer reference. 409 `CONFLICT` (`details.code` `COMMISSION_NOT_DUE`) when the milestone was not reached or it is already paid; 400 when it has no amount yet. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The commission id."
            },
            "required": true,
            "description": "The commission id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PayChannelCommissionBody"
              },
              "example": {
                "reference": "TRF-20261215-0042"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Paid",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ChannelCommission"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000703",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                    "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "basis": "percent",
                    "rate_value": 2,
                    "currency": "UF",
                    "milestone": "escritura",
                    "status": "paid",
                    "base_amount": null,
                    "amount": 70,
                    "due_at": null,
                    "paid_at": "2026-12-15T12:00:00.000Z",
                    "paid_reference": "TRF-20261215-0042",
                    "cancelled_reason": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/channel-commissions/{id}/cancel": {
      "post": {
        "summary": "Cancel a commission",
        "description": "For a desistimiento or an agreement that no longer holds. A paid commission cannot be cancelled (409 `CONFLICT`, `details.code` `COMMISSION_FINAL`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos: the channel is managed by whoever sees every proyecto.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The commission id."
            },
            "required": true,
            "description": "The commission id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancelChannelCommissionBody"
              },
              "example": {
                "reason": "Desistimiento"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ChannelCommission"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000703",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "partner_id": "0199e3a0-0000-7000-8000-000000000701",
                    "claim_id": "0199e3a0-0000-7000-8000-000000000702",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000704",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "basis": "percent",
                    "rate_value": 2,
                    "currency": null,
                    "milestone": "escritura",
                    "status": "cancelled",
                    "base_amount": null,
                    "amount": null,
                    "due_at": null,
                    "paid_at": null,
                    "paid_reference": null,
                    "cancelled_reason": "Desistimiento",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/toctoc-connection": {
      "get": {
        "summary": "The TocToc lead door of the workspace",
        "description": "Whether the inbound TocToc lead door exists, its last lead and the path it is served on. Requires `marketplace:read`. TocToc offers no API or webhook (spike #4427): its lead notifications arrive by the forwarded email at the workspace’s lead door, and publishing is assisted (export the payload, record the code TocToc returns). Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ToctocConnectionStatus"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": true,
                    "connection": {
                      "id": "0199e3a0-0000-7000-8000-000000000802",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "token_hint": "a1b2",
                      "last_lead_at": "2026-10-20T12:00:00.000Z",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    },
                    "lead_path": "/api/v1/integrations/toctoc/leads"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/toctoc-connection/token": {
      "post": {
        "summary": "Create or rotate the TocToc lead door token",
        "description": "Creates the workspace's lead door, or rotates its token (the previous URL stops working at once). The token is returned ONCE: only its hash is kept. POST the forwarded TocToc notification to `path`, as JSON: either a lead (`name`/`nombre`, `email`/`correo`, `phone`/`telefono`, `message`/`mensaje`, `project_code`/`codigo`, `project_name`/`proyecto`) or the email (`{ \"email\": { \"from\", \"subject\", \"text\", \"html\", \"message_id\" } }`). A body that yields no name, email or phone is kept for a person to read and creates no lead. Requires `marketplace:write`. TocToc offers no API or webhook (spike #4427): its lead notifications arrive by the forwarded email at the workspace’s lead door, and publishing is assisted (export the payload, record the code TocToc returns). Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "responses": {
          "201": {
            "description": "Issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ToctocToken"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connection": {
                      "id": "0199e3a0-0000-7000-8000-000000000802",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "token_hint": "a1b2",
                      "last_lead_at": "2026-10-20T12:00:00.000Z",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    },
                    "token": "tt_0f3c…a1b2",
                    "path": "/api/v1/integrations/toctoc/leads/tt_0f3c…a1b2"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/toctoc-listings": {
      "get": {
        "summary": "List TocToc publications and their proyecto",
        "description": "The TocToc publication codes seen on leads or recorded on a proyecto, with how many leads arrived on each. `mapped=false` is the queue of publications waiting for a proyecto: their leads were created with no proyecto and a note. Requires `marketplace:read`. TocToc offers no API or webhook (spike #4427): its lead notifications arrive by the forwarded email at the workspace’s lead door, and publishing is assisted (export the payload, record the code TocToc returns). Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "`false` = the publications still waiting for a proyecto (their leads are flagged for mapping)."
            },
            "required": false,
            "description": "`false` = the publications still waiting for a proyecto (their leads are flagged for mapping).",
            "name": "mapped",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Publications",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ToctocListingListItem"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000801",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "external_id": "TT-48213",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "title": "Parque Los Aromos",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "lead_count": 4,
                      "unmapped_lead_count": 0
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Map a TocToc publication code to a proyecto",
        "description": "Maps the code TocToc gave a publication to a proyecto ahead of its first lead. Idempotent on the code: posting an already-seen one re-maps it, stamps the proyecto on its waiting leads and routes the unowned ones to the proyecto's ejecutivos. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateToctocListingBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "external_id": "TT-48213"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Mapped",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MappedToctocListing"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000801",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "external_id": "TT-48213",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "title": "Parque Los Aromos",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "leads_mapped": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/toctoc-listings/{id}": {
      "patch": {
        "summary": "Map or unmap a TocToc publication",
        "description": "Sets the proyecto of a publication (`project_id`), or clears it with `null`. Mapping stamps the proyecto on the leads waiting on the publication and routes the unowned ones to its ejecutivos; unmapping only affects leads that arrive afterwards. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The TocToc listing id."
            },
            "required": true,
            "description": "The TocToc listing id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateToctocListingBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MappedToctocListing"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000801",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "external_id": "TT-48213",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "title": "Parque Los Aromos",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "leads_mapped": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/toctoc": {
      "get": {
        "summary": "A proyecto’s TocToc publishing payload and state",
        "description": "The payload a hand-over of the proyecto to TocToc carries (the proyecto, its tipologías and the live stock behind each, versioned by `schema`), what the listing still lacks (`issues`, empty = ready), and where the proyecto stands on TocToc (`publication`, null = a draft). Requires `marketplace:read`. TocToc offers no API or webhook (spike #4427): its lead notifications arrive by the forwarded email at the workspace’s lead door, and publishing is assisted (export the payload, record the code TocToc returns). Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Payload and state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ToctocProjectPublication"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "publication": {
                      "id": "0199e3a0-0000-7000-8000-000000000803",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "status": "live",
                      "external_ref": "TT-48213",
                      "notes": null,
                      "submitted_at": "2026-10-20T12:00:00.000Z",
                      "live_at": "2026-10-20T12:00:00.000Z",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    },
                    "payload": {
                      "schema": "vitrina.toctoc.project.v1",
                      "project": {
                        "reference": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "Parque Los Aromos",
                        "description": "Condominio de departamentos a pasos del metro.",
                        "stage": "en_verde",
                        "stage_label": "En verde",
                        "delivery_date": "2027-12-01",
                        "region": "Metropolitana",
                        "comuna": "La Florida",
                        "address": "Av. Vicuña Mackenna 0000",
                        "subsidies": [
                          "DS19"
                        ],
                        "photos": [
                          "https://media.example.com/t/other/aromos-1.jpg"
                        ],
                        "brochure_url": null
                      },
                      "unit_models": [
                        {
                          "reference": "0199e3a0-0000-7000-8000-000000000201",
                          "name": "3D2B 78 m²",
                          "description": null,
                          "bedrooms": 3,
                          "bathrooms": 2,
                          "usable_area_m2": 72.5,
                          "terrace_area_m2": 6,
                          "total_area_m2": 78.5,
                          "floor_plans": [],
                          "renders": [],
                          "tour_360_url": null,
                          "total_units": 24,
                          "available_units": 11,
                          "price": {
                            "currency": "UF",
                            "from": 3450,
                            "to": 3780
                          }
                        }
                      ],
                      "extras": {
                        "parking_available": 8,
                        "storage_available": 5
                      }
                    },
                    "issues": [
                      {
                        "field": "project.brochure_url",
                        "message": "Agrega el brochure del proyecto."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Record where a proyecto stands on TocToc",
        "description": "Records `draft`, `submitted` (handed to TocToc), `live` or `paused`, and the code TocToc gave the proyecto. `live` needs the code (400 otherwise). Recording a code maps it to the proyecto, so the leads that carry it land on it and those already waiting on it are moved; a code that belongs to another proyecto is refused (400). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Refused (403) to a caller whose role narrows proyectos.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateToctocPublicationBody"
              },
              "example": {
                "status": "live",
                "external_ref": "TT-48213"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Recorded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ToctocPublication"
                        },
                        {
                          "type": "object"
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000803",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "status": "live",
                    "external_ref": "TT-48213",
                    "notes": null,
                    "submitted_at": "2026-10-20T12:00:00.000Z",
                    "live_at": "2026-10-20T12:00:00.000Z",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/units": {
      "get": {
        "summary": "List unidades (a proyecto’s stock)",
        "description": "Live unidades of live proyectos, by floor, then kind, then number in natural order. Filters ANDed: `project_id`, `unit_model_id`, `kind`, `status`, `min_floor` / `max_floor`, `min_bedrooms`, free text `q`. `meta.status_counts` counts the same filters WITHOUT `status`, for a grid’s status chips. A unidad is a property row: `GET /properties` lists unidades too with `include_units=true`. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos (comma-separated ids)."
            },
            "required": false,
            "description": "Only these proyectos (comma-separated ids).",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these tipologías (comma-separated ids)."
            },
            "required": false,
            "description": "Only these tipologías (comma-separated ids).",
            "name": "unit_model_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "departamento",
                  "casa",
                  "estacionamiento",
                  "bodega"
                ],
                "description": "departamento | casa | estacionamiento | bodega"
              },
              "maxItems": 50,
              "description": "Comma-separated kinds."
            },
            "required": false,
            "description": "Comma-separated kinds.",
            "name": "kind",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "disponible",
                  "bloqueada",
                  "reservada",
                  "promesada",
                  "vendida",
                  "arrendada",
                  "pausada"
                ],
                "description": "Estado: disponible | bloqueada | reservada | promesada | vendida | pausada (arrendada never occurs on a unidad)."
              },
              "maxItems": 50,
              "description": "Comma-separated statuses."
            },
            "required": false,
            "description": "Comma-separated statuses.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": -10,
              "maximum": 300
            },
            "required": false,
            "name": "min_floor",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": -10,
              "maximum": 300
            },
            "required": false,
            "name": "max_floor",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 99
            },
            "required": false,
            "name": "min_bedrooms",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "Free text over the unit number, the title and the display id."
            },
            "required": false,
            "description": "Free text over the unit number, the title and the display id.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "description": "Default 100, max 500 (a whole proyecto fits in one page)."
            },
            "required": false,
            "description": "Default 100, max 500 (a whole proyecto fits in one page).",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Unidades",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Unit"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        },
                        "status_counts": {
                          "type": "object",
                          "additionalProperties": {
                            "type": "integer"
                          }
                        },
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "pagination",
                        "status_counts",
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "display_id": "I-42",
                      "title": "Departamento 1203 · Parque Los Aromos",
                      "description": null,
                      "operation": "venta",
                      "property_type": "departamento",
                      "kind": "departamento",
                      "status": "disponible",
                      "region": "Metropolitana",
                      "comuna": "La Florida",
                      "sector": null,
                      "address": null,
                      "sale_price_amount": 3250.5,
                      "sale_price_currency": "UF",
                      "rent_price_amount": null,
                      "rent_price_currency": null,
                      "common_expenses_clp": null,
                      "property_tax_clp": null,
                      "bedrooms": 3,
                      "bathrooms": 2,
                      "usable_area_m2": 72.5,
                      "terrace_area_m2": 6,
                      "total_area_m2": 78.5,
                      "parking_spaces": null,
                      "storage_units": null,
                      "orientation": "nororiente",
                      "floor": 12,
                      "year_built": null,
                      "furnished": null,
                      "pets_allowed": null,
                      "photos": [],
                      "broker_user_id": null,
                      "owner_name": null,
                      "owner_phone": null,
                      "owner_email": null,
                      "source": "import",
                      "external_ref": null,
                      "protected_fields": [],
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "unit_model_id": "0199e3a0-0000-7000-8000-000000000201",
                      "unit_number": "1203",
                      "hold_expires_at": null,
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "sale_price": {
                        "amount": 3250.5,
                        "currency": "UF",
                        "amount_clp": 128500000,
                        "amount_uf": 3250.5
                      },
                      "rent_price": null,
                      "project": {
                        "id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "Parque Los Aromos"
                      },
                      "unit_model": {
                        "id": "0199e3a0-0000-7000-8000-000000000201",
                        "name": "3D2B 78 m²"
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 100,
                      "offset": 0
                    },
                    "status_counts": {
                      "disponible": 1
                    },
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a unidad by hand",
        "description": "A departamento, casa, estacionamiento or bodega of a proyecto; estacionamientos and bodegas are unidades of their own, priced and sold alone or with a departamento. The number is unique per proyecto and kind (409 otherwise); the tipología must be one of the proyecto’s. Born `disponible`. A unidad’s status is its negocio’s: by hand it can only be paused and put back (disponible ⇄ pausada), and no import ever writes it. Once the proyecto has a published lista de precios (`/price-lists`), its unidades’ prices change only by publishing a new version: a price here answers 409 `unit_price_owned_by_price_list`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUnitBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "kind": "estacionamiento",
                "unit_number": "E-12",
                "floor": -1,
                "sale_price_amount": 250,
                "sale_price_currency": "UF"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Unit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000401",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-42",
                    "title": "Departamento 1203 · Parque Los Aromos",
                    "description": null,
                    "operation": "venta",
                    "property_type": "departamento",
                    "kind": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "sector": null,
                    "address": null,
                    "sale_price_amount": 3250.5,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": null,
                    "property_tax_clp": null,
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "parking_spaces": null,
                    "storage_units": null,
                    "orientation": "nororiente",
                    "floor": 12,
                    "year_built": null,
                    "furnished": null,
                    "pets_allowed": null,
                    "photos": [],
                    "broker_user_id": null,
                    "owner_name": null,
                    "owner_phone": null,
                    "owner_email": null,
                    "source": "import",
                    "external_ref": null,
                    "protected_fields": [],
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "unit_model_id": "0199e3a0-0000-7000-8000-000000000201",
                    "unit_number": "1203",
                    "hold_expires_at": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sale_price": {
                      "amount": 3250.5,
                      "currency": "UF",
                      "amount_clp": 128500000,
                      "amount_uf": 3250.5
                    },
                    "rent_price": null,
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "unit_model": {
                      "id": "0199e3a0-0000-7000-8000-000000000201",
                      "name": "3D2B 78 m²"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/units/import/columns": {
      "post": {
        "summary": "Import step 1: read a stock file’s columns",
        "description": "Multipart, the file in a part named `file` (.xlsx, .xls or .csv, up to 20 MB; the first sheet with data, the first non-empty row is the header). Returns the header, the first rows, the mapping we suggest from the column names and every field a column can feed — the mapping screen. Writes and stores nothing: send the same file to `POST /units/import`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary: stock-aromos.xlsx)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The file’s columns and the suggested mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitImportColumns"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "format": "xlsx",
                    "sheet_name": "Stock",
                    "header": [
                      "Número",
                      "Tipo",
                      "Tipología",
                      "Piso",
                      "Precio UF",
                      "Estado"
                    ],
                    "row_count": 200,
                    "sample": [
                      [
                        "1203",
                        "Depto",
                        "3D2B",
                        "12",
                        "3.250,5",
                        "Disponible"
                      ]
                    ],
                    "suggested_mapping": {
                      "unit_number": "Número",
                      "kind": "Tipo",
                      "unit_model": "Tipología",
                      "floor": "Piso",
                      "price_uf": "Precio UF"
                    },
                    "ignored_status_columns": [
                      "Estado"
                    ],
                    "fields": [
                      {
                        "key": "unit_number",
                        "label": "Número de unidad",
                        "required": true,
                        "description": "The unidad’s number as the inmobiliaria writes it."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/units/import": {
      "post": {
        "summary": "Import steps 2–3: dry run, then load a proyecto’s unidades",
        "description": "Loads a proyecto’s stock from the inmobiliaria’s Excel or CSV (multipart: `file`, `project_id`, `mapping` as JSON text, `dry_run`, `default_kind`) or from JSON `rows` (one object per unidad keyed by column name — the MCP path). **`dry_run` defaults to true**: the report says, row by row, what would be created, updated (which fields) or left unchanged, and every error with its field; nothing is written. Send `dry_run: false` to write every row without an error in ONE transaction. A unidad is matched by proyecto + kind + number, so re-importing a file changes nothing and an updated file updates in place; unidades missing from the file are counted (`not_in_file`), never removed. **An import never writes a unidad’s status** (there is no status field; a column like «Estado» is listed in `ignored_status_columns`), and fields a person edited by hand stay (`protected_skipped`). A tipología is named by its name in the proyecto; its dormitorios, baños and m² fill a new unidad’s when the file leaves them empty. Once the proyecto has a published lista de precios, the price column is left out (`ignored_price_column`): prices then come from the list only. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportUnitsBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "rows": [
                  {
                    "Número": "1203",
                    "Tipo": "Depto",
                    "Tipología": "3D2B",
                    "Piso": 12,
                    "Precio UF": "3.250,5"
                  },
                  {
                    "Número": "E-12",
                    "Tipo": "Estacionamiento",
                    "Piso": -1,
                    "Precio UF": 250
                  }
                ],
                "mapping": {
                  "unit_number": "Número",
                  "kind": "Tipo",
                  "unit_model": "Tipología",
                  "floor": "Piso",
                  "price_uf": "Precio UF"
                },
                "dry_run": true
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "project_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "mapping": {
                    "type": "string"
                  },
                  "dry_run": {
                    "type": "string"
                  },
                  "default_kind": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The import report (what would happen, or what happened)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitImportReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": true,
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "mapping": {
                      "unit_number": "Número",
                      "kind": "Tipo",
                      "unit_model": "Tipología",
                      "floor": "Piso",
                      "price_uf": "Precio UF"
                    },
                    "ignored_status_columns": [
                      "Estado"
                    ],
                    "ignored_price_column": null,
                    "summary": {
                      "total_rows": 3,
                      "create": 1,
                      "update": 1,
                      "unchanged": 0,
                      "error": 1,
                      "not_in_file": 0
                    },
                    "rows": [
                      {
                        "row_number": 2,
                        "action": "create",
                        "unit_number": "1203",
                        "kind": "departamento",
                        "unit_id": null,
                        "display_id": null,
                        "fields": [
                          "floor",
                          "sale_price_amount",
                          "sale_price_currency",
                          "unit_model_id"
                        ],
                        "protected_skipped": [],
                        "status": "disponible",
                        "errors": []
                      },
                      {
                        "row_number": 3,
                        "action": "update",
                        "unit_number": "E-12",
                        "kind": "estacionamiento",
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "display_id": "I-43",
                        "fields": [
                          "sale_price_amount"
                        ],
                        "protected_skipped": [],
                        "status": "reservada",
                        "errors": []
                      },
                      {
                        "row_number": 4,
                        "action": "error",
                        "unit_number": "1204",
                        "kind": "departamento",
                        "unit_id": null,
                        "display_id": null,
                        "fields": [],
                        "protected_skipped": [],
                        "status": "disponible",
                        "errors": [
                          {
                            "field": "unit_model",
                            "message": "La tipología «4D3B» no existe en este proyecto. Créala primero o corrige el nombre."
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/units/{id}": {
      "get": {
        "summary": "One unidad",
        "description": "By uuid or display id (`I-42`). Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The unidad’s uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The unidad’s uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Unidad",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Unit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000401",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-42",
                    "title": "Departamento 1203 · Parque Los Aromos",
                    "description": null,
                    "operation": "venta",
                    "property_type": "departamento",
                    "kind": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "sector": null,
                    "address": null,
                    "sale_price_amount": 3250.5,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": null,
                    "property_tax_clp": null,
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "parking_spaces": null,
                    "storage_units": null,
                    "orientation": "nororiente",
                    "floor": 12,
                    "year_built": null,
                    "furnished": null,
                    "pets_allowed": null,
                    "photos": [],
                    "broker_user_id": null,
                    "owner_name": null,
                    "owner_phone": null,
                    "owner_email": null,
                    "source": "import",
                    "external_ref": null,
                    "protected_fields": [],
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "unit_model_id": "0199e3a0-0000-7000-8000-000000000201",
                    "unit_number": "1203",
                    "hold_expires_at": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sale_price": {
                      "amount": 3250.5,
                      "currency": "UF",
                      "amount_clp": 128500000,
                      "amount_uf": 3250.5
                    },
                    "rent_price": null,
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "unit_model": {
                      "id": "0199e3a0-0000-7000-8000-000000000201",
                      "name": "3D2B 78 m²"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a unidad",
        "description": "Takes it out of the stock. A unidad a negocio holds (bloqueada, reservada, promesada, vendida) cannot be removed (409 `unit_held_by_negocio`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The unidad’s uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The unidad’s uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "display_id": {
                          "type": "string"
                        },
                        "unit_number": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "display_id",
                        "unit_number",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000401",
                    "display_id": "I-42",
                    "unit_number": "1203",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a unidad",
        "description": "Partial update; `null` clears a field. The proyecto is fixed. A field edited by hand on an imported unidad is protected from the next import (release it with `POST /units/{id}/release-fields`). A unidad’s status is its negocio’s: by hand it can only be paused and put back (disponible ⇄ pausada), and no import ever writes it. A status change while a negocio holds the unidad is a 409 `unit_status_owned_by_negocio`. Once the proyecto has a published lista de precios (`/price-lists`), its unidades’ prices change only by publishing a new version: a price here answers 409 `unit_price_owned_by_price_list`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The unidad’s uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The unidad’s uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUnitBody"
              },
              "example": {
                "status": "pausada"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Unit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000401",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-42",
                    "title": "Departamento 1203 · Parque Los Aromos",
                    "description": null,
                    "operation": "venta",
                    "property_type": "departamento",
                    "kind": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "sector": null,
                    "address": null,
                    "sale_price_amount": 3250.5,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": null,
                    "property_tax_clp": null,
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "parking_spaces": null,
                    "storage_units": null,
                    "orientation": "nororiente",
                    "floor": 12,
                    "year_built": null,
                    "furnished": null,
                    "pets_allowed": null,
                    "photos": [],
                    "broker_user_id": null,
                    "owner_name": null,
                    "owner_phone": null,
                    "owner_email": null,
                    "source": "import",
                    "external_ref": null,
                    "protected_fields": [],
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "unit_model_id": "0199e3a0-0000-7000-8000-000000000201",
                    "unit_number": "1203",
                    "hold_expires_at": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sale_price": {
                      "amount": 3250.5,
                      "currency": "UF",
                      "amount_clp": 128500000,
                      "amount_uf": 3250.5
                    },
                    "rent_price": null,
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "unit_model": {
                      "id": "0199e3a0-0000-7000-8000-000000000201",
                      "name": "3D2B 78 m²"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/units/{id}/release-fields": {
      "post": {
        "summary": "Let a unidad’s protected fields follow the import again",
        "description": "Removes fields from `protected_fields`, so the next import may overwrite them. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The unidad’s uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The unidad’s uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "fields": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "minItems": 1,
                    "maxItems": 40,
                    "description": "Protected fields to hand back to the stock import, so the next import may overwrite them again."
                  }
                },
                "required": [
                  "fields"
                ]
              },
              "example": {
                "fields": [
                  "sale_price_amount"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Unit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000401",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "display_id": "I-42",
                    "title": "Departamento 1203 · Parque Los Aromos",
                    "description": null,
                    "operation": "venta",
                    "property_type": "departamento",
                    "kind": "departamento",
                    "status": "disponible",
                    "region": "Metropolitana",
                    "comuna": "La Florida",
                    "sector": null,
                    "address": null,
                    "sale_price_amount": 3250.5,
                    "sale_price_currency": "UF",
                    "rent_price_amount": null,
                    "rent_price_currency": null,
                    "common_expenses_clp": null,
                    "property_tax_clp": null,
                    "bedrooms": 3,
                    "bathrooms": 2,
                    "usable_area_m2": 72.5,
                    "terrace_area_m2": 6,
                    "total_area_m2": 78.5,
                    "parking_spaces": null,
                    "storage_units": null,
                    "orientation": "nororiente",
                    "floor": 12,
                    "year_built": null,
                    "furnished": null,
                    "pets_allowed": null,
                    "photos": [],
                    "broker_user_id": null,
                    "owner_name": null,
                    "owner_phone": null,
                    "owner_email": null,
                    "source": "import",
                    "external_ref": null,
                    "protected_fields": [],
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "unit_model_id": "0199e3a0-0000-7000-8000-000000000201",
                    "unit_number": "1203",
                    "hold_expires_at": null,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "sale_price": {
                      "amount": 3250.5,
                      "currency": "UF",
                      "amount_clp": 128500000,
                      "amount_uf": 3250.5
                    },
                    "rent_price": null,
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "unit_model": {
                      "id": "0199e3a0-0000-7000-8000-000000000201",
                      "name": "3D2B 78 m²"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/datahunter-import/columns": {
      "post": {
        "summary": "Datahunter import step 1: read the files’ columns",
        "description": "Multipart, one part per file, named `proyectos`, `unidades`, `leads`, `propietarios`, `cotizaciones` or `negocios` (.xlsx, .xls or .csv, up to 20 MB each, any subset). For each file returns its header, the first rows, the mapping we suggest from the column names and every field a column can feed — the mapping screen. A file whose columns are named by the field keys (what the #4806 extractor writes) maps with no input. Writes and stores nothing. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Needs a caller who sees every proyecto (a narrowed ejecutivo answers 403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "proyectos": {},
                  "unidades": {},
                  "leads": {},
                  "propietarios": {},
                  "cotizaciones": {},
                  "negocios": {}
                }
              },
              "example": {
                "leads": "(binary: leads-datahunter.xlsx)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Each file’s columns and the suggested mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DatahunterImportColumns"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "files": [
                      {
                        "entity": "leads",
                        "format": "xlsx",
                        "sheet_name": "Leads",
                        "header": [
                          "Nombre",
                          "RUT",
                          "Celular",
                          "Proyecto",
                          "Etapa",
                          "Fecha"
                        ],
                        "row_count": 1840,
                        "sample": [
                          [
                            "Ana Soto",
                            "12.345.678-5",
                            "+56 9 8765 4321",
                            "Parque Aromos",
                            "Cotizó",
                            "03/05/2024"
                          ]
                        ],
                        "suggested_mapping": {
                          "name": "Nombre",
                          "rut": "RUT",
                          "phone": "Celular",
                          "project": "Proyecto",
                          "stage": "Etapa",
                          "created_at": "Fecha"
                        },
                        "ignored_status_columns": [],
                        "fields": [
                          {
                            "key": "name",
                            "label": "Nombre",
                            "required": true,
                            "description": "The person’s full name."
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/datahunter-import": {
      "post": {
        "summary": "Datahunter import steps 2–3: dry run, then load the history",
        "description": "Loads Datahunter's history as records (decision D18): **proyectos** (matched by name), **unidades** with their tipologías (the stock import's rules: proyecto + kind + number, protected fields kept), **leads**, **propietarios**, **cotizaciones** and **negocios**. Send the files as multipart parts (`proyectos`, `unidades`, `leads`, `propietarios`, `cotizaciones`, `negocios`; any subset) with `mapping` as JSON text, or JSON `rows` per file (the MCP and extractor path). **`dry_run` defaults to true**: all the files are planned together and the report says what would be created, updated, left unchanged, skipped (repeats) or rejected, row by row for the problems, and **balances against the source row counts** (`totals.balanced`). People (leads, owners, buyers) are matched to existing contacts by RUT, then phone, then email, and the same person on several rows is one contact; a matched contact is never rewritten. **Imported leads are history**: contacts are import-only (no conversation, no automation reaches them), leads keep their original dates and carry source `import`, and nothing is sent. Lead etapas map to the pipeline’s stages through `stage_mapping` (the dry run lists them in `lead_stages`). **Cotizaciones and negocios are staged** (counted in `staged`) until the cotización and negocio tables exist; their contact, proyecto and unidad are already resolved. A negocio or owner raises its unidad’s status (reservada, promesada, vendida) and never lowers it. Send `dry_run: false` with `step` to run one file at a time (the screen’s progress), in the order proyectos, unidades, leads, propietarios, cotizaciones, negocios; each step is its own batch and re-running a step changes nothing. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Needs a caller who sees every proyecto (a narrowed ejecutivo answers 403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RunDatahunterImportBody"
              },
              "example": {
                "rows": {
                  "leads": [
                    {
                      "name": "Ana Soto",
                      "rut": "12.345.678-5",
                      "phone": "+56987654321",
                      "project": "Parque Aromos",
                      "stage": "Cotizó",
                      "created_at": "03/05/2024"
                    }
                  ]
                },
                "dry_run": true
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "proyectos": {},
                  "unidades": {},
                  "leads": {},
                  "propietarios": {},
                  "cotizaciones": {},
                  "negocios": {},
                  "mapping": {
                    "type": "string"
                  },
                  "pipeline_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "stage_mapping": {
                    "type": "string"
                  },
                  "step": {
                    "type": "string"
                  },
                  "dry_run": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reconciliation report (what would happen, or what happened)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DatahunterImportReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": true,
                    "step": null,
                    "batch_ids": [],
                    "mapping": {
                      "leads": {
                        "name": "Nombre",
                        "project": "Proyecto"
                      }
                    },
                    "ignored_status_columns": [],
                    "pipeline": {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "name": "Ventas"
                    },
                    "entities": {
                      "leads": {
                        "summary": {
                          "source_rows": 1840,
                          "create": 1790,
                          "update": 0,
                          "unchanged": 0,
                          "skipped": 12,
                          "error": 38,
                          "balanced": true
                        },
                        "issues": [],
                        "issues_truncated": false,
                        "sample": []
                      }
                    },
                    "totals": {
                      "source_rows": 1840,
                      "create": 1790,
                      "update": 0,
                      "unchanged": 0,
                      "skipped": 12,
                      "error": 38,
                      "balanced": true
                    },
                    "contacts": {
                      "people": 1604,
                      "create": 1590,
                      "match_existing": 14,
                      "merged_in_file": 186
                    },
                    "unit_models_to_create": 0,
                    "unit_status_changes": {
                      "reservada": 0,
                      "promesada": 0,
                      "vendida": 0,
                      "unchanged": 0
                    },
                    "lead_stages": [],
                    "negocios_by_stage": {
                      "reserva": 0,
                      "promesa": 0,
                      "escritura": 0,
                      "entrega": 0,
                      "desistido": 0
                    },
                    "staged": {
                      "cotizacion": 0,
                      "negocio": 0
                    },
                    "written": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/datahunter-import/batches": {
      "get": {
        "summary": "Datahunter import: past batches and what is staged",
        "description": "The committed steps, newest first, each with its reconciliation counts, plus how many carried-over records wait in each state (`staged` cotizaciones and negocios wait for their tables). Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. Needs a caller who sees every proyecto (a narrowed ejecutivo answers 403).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "responses": {
          "200": {
            "description": "Batches and staged counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DatahunterImportBatches"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "batches": [],
                    "staged": [
                      {
                        "entity": "negocio",
                        "state": "staged",
                        "n": 212
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/floor-plans": {
      "get": {
        "summary": "List a proyecto’s floor plans",
        "description": "The proyecto’s plans in display order (`sort_order`, then floor, then name), each with the unidades placed on it as polygons normalised to the image. The plans feed the embeddable floor-plan visor, which colours each placed unidad by its live status on the inmobiliaria’s own site; whether it shows prices is the proyecto’s `visor_show_prices`. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Floor plans",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/FloorPlan"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Piso 12",
                      "floor": 12,
                      "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                      "image_width": 1600,
                      "image_height": 1000,
                      "sort_order": 12,
                      "created_by": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z",
                      "units": [
                        {
                          "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                          "shape": [
                            [
                              0.1,
                              0.2
                            ],
                            [
                              0.3,
                              0.2
                            ],
                            [
                              0.3,
                              0.45
                            ],
                            [
                              0.1,
                              0.45
                            ]
                          ]
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a floor plan to a proyecto",
        "description": "A plan image (a floor, a planta tipo, a site plan of casas). Upload the image with `POST /tenant/media` first and send its `url`; send its pixel size too so the visor reserves the aspect ratio. The name is unique inside the proyecto (409 otherwise). Place unidades with `PUT /floor-plans/{id}/units`. The plans feed the embeddable floor-plan visor, which colours each placed unidad by its live status on the inmobiliaria’s own site; whether it shows prices is the proyecto’s `visor_show_prices`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateFloorPlanBody"
              },
              "example": {
                "name": "Piso 12",
                "floor": 12,
                "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                "image_width": 1600,
                "image_height": 1000,
                "sort_order": 12
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The plan, with no unidades yet",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FloorPlan"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "Piso 12",
                    "floor": 12,
                    "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                    "image_width": 1600,
                    "image_height": 1000,
                    "sort_order": 12,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "units": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "shape": [
                          [
                            0.1,
                            0.2
                          ],
                          [
                            0.3,
                            0.2
                          ],
                          [
                            0.3,
                            0.45
                          ],
                          [
                            0.1,
                            0.45
                          ]
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/floor-plans/{id}": {
      "get": {
        "summary": "Get a floor plan",
        "description": "One plan with the live unidades placed on it. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The plan id."
            },
            "required": true,
            "description": "The plan id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FloorPlan"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "Piso 12",
                    "floor": 12,
                    "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                    "image_width": 1600,
                    "image_height": 1000,
                    "sort_order": 12,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "units": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "shape": [
                          [
                            0.1,
                            0.2
                          ],
                          [
                            0.3,
                            0.2
                          ],
                          [
                            0.3,
                            0.45
                          ],
                          [
                            0.1,
                            0.45
                          ]
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a floor plan",
        "description": "Removes the plan and its placements. The unidades themselves are untouched. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The plan id."
            },
            "required": true,
            "description": "The plan id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "deleted": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a floor plan",
        "description": "Rename it, change its floor, order or image. Changing the image keeps the placements: their coordinates are fractions of the image, so a re-export of the same plan at another size still fits. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The plan id."
            },
            "required": true,
            "description": "The plan id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateFloorPlanBody"
              },
              "example": {
                "name": "Piso 12 (torre A)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The plan, edited",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FloorPlan"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "Piso 12",
                    "floor": 12,
                    "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                    "image_width": 1600,
                    "image_height": 1000,
                    "sort_order": 12,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "units": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "shape": [
                          [
                            0.1,
                            0.2
                          ],
                          [
                            0.3,
                            0.2
                          ],
                          [
                            0.3,
                            0.45
                          ],
                          [
                            0.1,
                            0.45
                          ]
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/floor-plans/{id}/units": {
      "put": {
        "summary": "Place unidades on a floor plan",
        "description": "Replaces every placement of the plan with `units`: each a unidad of the plan’s own proyecto, once, with a polygon of 3 to 64 `[x, y]` points as fractions (0..1) of the image, origin top-left. A unidad of another proyecto, a removed one or a repeated one is a 400 naming the ids, and nothing is written. An empty list clears the plan. A unidad may sit on several plans (a dúplex). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The plan id."
            },
            "required": true,
            "description": "The plan id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetFloorPlanUnitsBody"
              },
              "example": {
                "units": [
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                    "shape": [
                      [
                        0.1,
                        0.2
                      ],
                      [
                        0.3,
                        0.2
                      ],
                      [
                        0.3,
                        0.45
                      ],
                      [
                        0.1,
                        0.45
                      ]
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The plan with its unidades",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/FloorPlan"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "tenant_id": "0199e3a0-0000-7000-8000-0000000000aa",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "name": "Piso 12",
                    "floor": 12,
                    "image_url": "https://media.example.com/t/other/aromos-piso-12.png",
                    "image_width": 1600,
                    "image_height": 1000,
                    "sort_order": 12,
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z",
                    "units": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "shape": [
                          [
                            0.1,
                            0.2
                          ],
                          [
                            0.3,
                            0.2
                          ],
                          [
                            0.3,
                            0.45
                          ],
                          [
                            0.1,
                            0.45
                          ]
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-lists": {
      "get": {
        "summary": "List lista de precios versions",
        "description": "The versions of the proyectos’ listas de precios, newest first within each proyecto (without their prices: `GET /price-lists/{id}`). Filters: `project_id`, `status` (draft | published), `current=true` (only each proyecto’s current list). A version’s `id` is the stable `price_list_version_id` a cotización records. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos (comma-separated ids)."
            },
            "required": false,
            "description": "Only these proyectos (comma-separated ids).",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "draft",
                  "published"
                ],
                "description": "draft: being built, editable. published: frozen; the newest published version is the proyecto’s current list."
              },
              "maxItems": 50,
              "description": "Comma-separated statuses."
            },
            "required": false,
            "description": "Comma-separated statuses.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean",
              "description": "true: only each proyecto’s current (published) version."
            },
            "required": false,
            "description": "true: only each proyecto’s current (published) version.",
            "name": "current",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Default 50."
            },
            "required": false,
            "description": "Default 50.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PriceList"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project": {
                        "id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "Parque Los Aromos"
                      },
                      "version_number": 2,
                      "status": "published",
                      "is_current": true,
                      "name": "Lista noviembre 2026",
                      "notes": null,
                      "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                      "entry_count": 200,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:30:00.000Z",
                      "published_at": "2026-10-20T12:30:00.000Z",
                      "published_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "published_by_kind": "ui_member",
                      "superseded_at": null,
                      "superseded_by_version_id": null
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Open a draft lista de precios",
        "description": "Opens the proyecto’s next version (v1, v2…) as a draft, by default with the current list’s prices copied in (each keeps who set it and when). One draft per proyecto: a second is a 409 `price_list_draft_exists` with the draft’s id in `details.draft_id`. Fill it with `POST /price-lists/{id}/import` or `PATCH /price-lists/{id}/entries`, then publish it. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePriceListBody"
              },
              "example": {
                "project_id": "0199e3a0-0000-7000-8000-000000000101",
                "name": "Lista noviembre 2026",
                "copy_from": "current"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft opened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "version_number": 2,
                    "status": "draft",
                    "is_current": false,
                    "name": "Lista noviembre 2026",
                    "notes": null,
                    "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "entry_count": 200,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:30:00.000Z",
                    "published_at": null,
                    "published_by": null,
                    "published_by_kind": null,
                    "superseded_at": null,
                    "superseded_by_version_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/price-lists/import/columns": {
      "post": {
        "summary": "Price import step 1: read a file’s columns",
        "description": "Multipart, the file in a part named `file` (.xlsx, .xls or .csv, up to 20 MB). Returns the header, the first rows and the mapping we suggest for the three fields a price row needs (unit_number, kind, price_uf). Writes nothing: send the same file to `POST /price-lists/{id}/import`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary: lista-aromos.xlsx)"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The file’s columns and the suggested mapping",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceImportColumns"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "format": "xlsx",
                    "sheet_name": "Lista",
                    "header": [
                      "N° Unidad",
                      "Tipo",
                      "Precio UF"
                    ],
                    "row_count": 200,
                    "sample": [
                      [
                        "1203",
                        "Depto",
                        "3.310"
                      ]
                    ],
                    "suggested_mapping": {
                      "unit_number": "N° Unidad",
                      "kind": "Tipo",
                      "price_uf": "Precio UF"
                    },
                    "fields": [
                      {
                        "key": "price_uf",
                        "label": "Precio (UF)",
                        "required": true,
                        "description": "List price in UF, two decimals."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/price-lists/{id}": {
      "get": {
        "summary": "One lista de precios version, with its prices",
        "description": "Every unidad price of the version in UF and in CLP with the day’s UF (`meta.uf_value`), who set each price and when, and the price in the version it is compared with (a draft: the current list; a published version: the one it replaced) for a diff. Old versions stay readable forever. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Version",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceListDetail"
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ],
                          "description": "The day’s UF the CLP amounts were computed with."
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "version_number": 2,
                    "status": "published",
                    "is_current": true,
                    "name": "Lista noviembre 2026",
                    "notes": null,
                    "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "entry_count": 200,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:30:00.000Z",
                    "published_at": "2026-10-20T12:30:00.000Z",
                    "published_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "published_by_kind": "ui_member",
                    "superseded_at": null,
                    "superseded_by_version_id": null,
                    "compared_with_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "summary": {
                      "entries": 200,
                      "added": 0,
                      "changed": 150,
                      "unchanged": 50,
                      "units_without_price": 0,
                      "removed_units": 0
                    },
                    "entries": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "display_id": "I-42",
                        "kind": "departamento",
                        "unit_number": "1203",
                        "title": "Departamento 1203 · Parque Los Aromos",
                        "floor": 12,
                        "unit_model": {
                          "id": "0199e3a0-0000-7000-8000-000000000201",
                          "name": "3D2B 78 m²"
                        },
                        "unit_status": "disponible",
                        "unit_removed": false,
                        "price_uf": 3310,
                        "price_clp": 130851317,
                        "previous_price_uf": 3250.5,
                        "source": "import",
                        "set_by": "0199e3a0-0000-7000-8000-0000000000bb",
                        "set_by_kind": "ui_member",
                        "set_at": "2026-10-20T12:10:00.000Z"
                      }
                    ]
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard a draft lista de precios",
        "description": "Deletes the draft and its prices; its version number is not reused. Drafts only: a published version is frozen (409 `price_list_not_draft`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Discarded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Rename a draft lista de precios",
        "description": "Name and notes. Drafts only: a published version is frozen (409 `price_list_not_draft`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdatePriceListBody"
              },
              "example": {
                "name": "Lista diciembre 2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "version_number": 2,
                    "status": "draft",
                    "is_current": false,
                    "name": "Lista noviembre 2026",
                    "notes": null,
                    "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "entry_count": 200,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:30:00.000Z",
                    "published_at": null,
                    "published_by": null,
                    "published_by_kind": null,
                    "superseded_at": null,
                    "superseded_by_version_id": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-lists/{id}/entries": {
      "patch": {
        "summary": "Set prices in a draft lista de precios",
        "description": "Sets (or, with `price_uf: null`, removes) the price of one or more unidades of the proyecto, all or nothing; each price is stamped with who set it and when. Answers the whole version, with `meta.changes`. Drafts only: a published version is frozen (409 `price_list_not_draft`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetPriceListEntriesBody"
              },
              "example": {
                "entries": [
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                    "price_uf": 3310
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The version after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceListDetail"
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ],
                          "description": "The day’s UF the CLP amounts were computed with."
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000501",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Parque Los Aromos"
                    },
                    "version_number": 2,
                    "status": "published",
                    "is_current": true,
                    "name": "Lista noviembre 2026",
                    "notes": null,
                    "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "entry_count": 200,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:30:00.000Z",
                    "published_at": "2026-10-20T12:30:00.000Z",
                    "published_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "published_by_kind": "ui_member",
                    "superseded_at": null,
                    "superseded_by_version_id": null,
                    "compared_with_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "summary": {
                      "entries": 200,
                      "added": 0,
                      "changed": 150,
                      "unchanged": 50,
                      "units_without_price": 0,
                      "removed_units": 0
                    },
                    "entries": [
                      {
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "display_id": "I-42",
                        "kind": "departamento",
                        "unit_number": "1203",
                        "title": "Departamento 1203 · Parque Los Aromos",
                        "floor": 12,
                        "unit_model": {
                          "id": "0199e3a0-0000-7000-8000-000000000201",
                          "name": "3D2B 78 m²"
                        },
                        "unit_status": "disponible",
                        "unit_removed": false,
                        "price_uf": 3310,
                        "price_clp": 130851317,
                        "previous_price_uf": 3250.5,
                        "source": "import",
                        "set_by": "0199e3a0-0000-7000-8000-0000000000bb",
                        "set_by_kind": "ui_member",
                        "set_at": "2026-10-20T12:10:00.000Z"
                      }
                    ]
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-lists/{id}/import": {
      "post": {
        "summary": "Price import steps 2–3: dry run, then load prices into a draft",
        "description": "Loads prices from the inmobiliaria’s Excel or CSV (multipart: `file`, `mapping` as JSON text, `dry_run`, `default_kind`) or from JSON `rows` (the MCP path). **`dry_run` defaults to true**: the report says row by row what would be added, changed or left unchanged (with the draft’s and the current list’s price), and every error with its field; nothing is written. `dry_run: false` writes every row without an error in ONE transaction. A row is matched to a unidad of the proyecto by kind + number; a unidad the proyecto does not have is an error (load the stock first with `POST /units/import`). Unidades missing from the file keep the draft’s price (`not_in_file`). Drafts only: a published version is frozen (409 `price_list_not_draft`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ImportPricesBody"
              },
              "example": {
                "rows": [
                  {
                    "Número": "1203",
                    "Tipo": "Depto",
                    "Precio UF": "3.310"
                  },
                  {
                    "Número": "E-12",
                    "Tipo": "Estacionamiento",
                    "Precio UF": 260
                  }
                ],
                "mapping": {
                  "unit_number": "Número",
                  "kind": "Tipo",
                  "price_uf": "Precio UF"
                },
                "dry_run": true
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {},
                  "mapping": {
                    "type": "string"
                  },
                  "dry_run": {
                    "type": "string"
                  },
                  "default_kind": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The import report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceImportReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "dry_run": true,
                    "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "mapping": {
                      "unit_number": "Número",
                      "kind": "Tipo",
                      "price_uf": "Precio UF"
                    },
                    "summary": {
                      "total_rows": 2,
                      "add": 0,
                      "change": 1,
                      "unchanged": 0,
                      "error": 1,
                      "not_in_file": 198
                    },
                    "rows": [
                      {
                        "row_number": 2,
                        "action": "change",
                        "unit_number": "1203",
                        "kind": "departamento",
                        "unit_id": "0199e3a0-0000-7000-8000-000000000401",
                        "display_id": "I-42",
                        "price_uf": 3310,
                        "draft_price_uf": 3250.5,
                        "current_price_uf": 3250.5,
                        "errors": []
                      },
                      {
                        "row_number": 3,
                        "action": "error",
                        "unit_number": "E-12",
                        "kind": "estacionamiento",
                        "unit_id": null,
                        "display_id": null,
                        "price_uf": 260,
                        "draft_price_uf": null,
                        "current_price_uf": null,
                        "errors": [
                          {
                            "field": "row",
                            "message": "La unidad estacionamiento E-12 no existe en este proyecto. Agrégala primero en Unidades."
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/price-lists/{id}/publish": {
      "post": {
        "summary": "Publish a lista de precios version",
        "description": "In one transaction: the proyecto’s current version is superseded (it stays readable), this draft becomes the published, current version, stamped with who published it and when, and every price is written onto its unidad (`sale_price_amount` in UF). The list is then the only writer of the proyecto’s unidad prices: `sale_price_amount` leaves a unidad’s `protected_fields`, a hand edit of the price answers 409 `unit_price_owned_by_price_list`, and the stock import ignores its price column. Unidades without a price in the version keep theirs (`units_without_price`). A cotización priced from an older version keeps that version’s prices. An empty draft is a 409 `price_list_empty`. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The version’s id (`price_list_version_id`)."
            },
            "required": true,
            "description": "The version’s id (`price_list_version_id`).",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Published",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PriceListPublishReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "price_list": {
                      "id": "0199e3a0-0000-7000-8000-000000000501",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project": {
                        "id": "0199e3a0-0000-7000-8000-000000000101",
                        "name": "Parque Los Aromos"
                      },
                      "version_number": 2,
                      "status": "published",
                      "is_current": true,
                      "name": "Lista noviembre 2026",
                      "notes": null,
                      "based_on_version_id": "0199e3a0-0000-7000-8000-000000000500",
                      "entry_count": 200,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:30:00.000Z",
                      "published_at": "2026-10-20T12:30:00.000Z",
                      "published_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "published_by_kind": "ui_member",
                      "superseded_at": null,
                      "superseded_by_version_id": null
                    },
                    "superseded_version_id": "0199e3a0-0000-7000-8000-000000000500",
                    "units_priced": 200,
                    "units_changed": 150,
                    "skipped_removed": 0,
                    "units_without_price": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/units/{id}/price-history": {
      "get": {
        "summary": "A unidad’s price in every published lista de precios",
        "description": "Newest version first: the price in UF and in CLP with the day’s UF (`meta.uf_value`), who set and who published it, and when. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The unidad’s uuid or its display id (`I-12`)."
            },
            "required": true,
            "description": "The unidad’s uuid or its display id (`I-12`).",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Price history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UnitPriceHistoryEntry"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ],
                          "description": "The day’s UF the CLP amounts were computed with."
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                      "version_number": 2,
                      "name": "Lista noviembre 2026",
                      "is_current": true,
                      "published_at": "2026-10-20T12:30:00.000Z",
                      "published_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "published_by_kind": "ui_member",
                      "superseded_at": null,
                      "price_uf": 3310,
                      "price_clp": 130851317,
                      "set_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "set_by_kind": "ui_member",
                      "set_at": "2026-10-20T12:10:00.000Z"
                    }
                  ],
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones": {
      "get": {
        "summary": "List cotizaciones",
        "description": "Newest first, without lines or financing (`GET /cotizaciones/{id}`). Filters: `lead_id`, `project_id`, `status`. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only this lead’s cotizaciones."
            },
            "required": false,
            "description": "Only this lead’s cotizaciones.",
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos (comma-separated ids)."
            },
            "required": false,
            "description": "Only these proyectos (comma-separated ids).",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "draft",
                  "sent",
                  "accepted",
                  "rejected",
                  "voided"
                ],
                "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
              },
              "maxItems": 50,
              "description": "Comma-separated statuses."
            },
            "required": false,
            "description": "Comma-separated statuses.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Default 50."
            },
            "required": false,
            "description": "Default 50.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Cotizaciones",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "display_id": {
                            "type": "string",
                            "description": "`CZ-12`; empty on a calculation."
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "sent",
                              "accepted",
                              "rejected",
                              "voided"
                            ],
                            "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                          },
                          "buyer_name": {
                            "type": "string"
                          },
                          "lead_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "contact_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "conversation_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "owner_user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The ejecutivo who answers for it."
                          },
                          "project_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "project_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "price_list_version_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid",
                            "description": "The lista de precios version it was priced from (frozen)."
                          },
                          "price_list_version_number": {
                            "type": [
                              "integer",
                              "null"
                            ]
                          },
                          "price_list_superseded": {
                            "type": "boolean",
                            "description": "true: a newer list was published since. A draft must be re-priced before sending."
                          },
                          "currency": {
                            "type": "string",
                            "enum": [
                              "UF",
                              "CLP"
                            ],
                            "description": "The currency the offer is written in."
                          },
                          "uf_value_clp": {
                            "type": [
                              "number",
                              "null"
                            ],
                            "description": "The UF used, frozen with the offer."
                          },
                          "uf_value_date": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "valid_until": {
                            "type": "string"
                          },
                          "discount_type": {
                            "type": "string",
                            "enum": [
                              "percent",
                              "amount"
                            ],
                            "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                          },
                          "discount_value": {
                            "type": "number"
                          },
                          "list_total": {
                            "type": "number"
                          },
                          "line_discount_total": {
                            "type": "number"
                          },
                          "global_discount_total": {
                            "type": "number"
                          },
                          "discount_total": {
                            "type": "number"
                          },
                          "total": {
                            "type": "number"
                          },
                          "discount_percent": {
                            "type": "number",
                            "description": "Effective discount over the list total, in percent."
                          },
                          "notes": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "template_version": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "presentation": {
                            "type": "object",
                            "properties": {
                              "ejecutivo": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "phone": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "email": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "name",
                                  "phone",
                                  "email"
                                ],
                                "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                              },
                              "sala": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "address": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "hours": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "name",
                                  "address",
                                  "hours"
                                ],
                                "description": "The proyecto’s sala de ventas, when it has one."
                              },
                              "image_url": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "The tipología’s floor plan (or render) printed on the PDF."
                              }
                            },
                            "required": [
                              "ejecutivo",
                              "sala",
                              "image_url"
                            ],
                            "description": "Who to call and where, printed on the PDF and frozen with the offer."
                          },
                          "created_by": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_by_kind": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "updated_at": {
                            "type": "string"
                          },
                          "sent_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "accepted_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "rejected_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "voided_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "voided_reason": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "is_expired": {
                            "type": "boolean"
                          },
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          }
                        },
                        "required": [
                          "display_id",
                          "status",
                          "buyer_name",
                          "lead_id",
                          "contact_id",
                          "conversation_id",
                          "owner_user_id",
                          "project_id",
                          "project_name",
                          "price_list_version_id",
                          "price_list_version_number",
                          "price_list_superseded",
                          "currency",
                          "uf_value_clp",
                          "uf_value_date",
                          "valid_until",
                          "discount_type",
                          "discount_value",
                          "list_total",
                          "line_discount_total",
                          "global_discount_total",
                          "discount_total",
                          "total",
                          "discount_percent",
                          "notes",
                          "template_version",
                          "presentation",
                          "created_by",
                          "created_by_kind",
                          "created_at",
                          "updated_at",
                          "sent_at",
                          "accepted_at",
                          "rejected_at",
                          "voided_at",
                          "voided_reason",
                          "is_expired",
                          "id"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        },
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "pagination",
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000901",
                      "display_id": "CZ-12",
                      "status": "sent",
                      "buyer_name": "Camila Rojas",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": null,
                      "conversation_id": null,
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                      "price_list_version_number": 2,
                      "price_list_superseded": false,
                      "currency": "UF",
                      "uf_value_clp": 39532.12,
                      "uf_value_date": "2026-10-20",
                      "valid_until": "2026-10-27",
                      "discount_type": "percent",
                      "discount_value": 0,
                      "list_total": 3510,
                      "line_discount_total": 0,
                      "global_discount_total": 0,
                      "discount_total": 0,
                      "total": 3510,
                      "discount_percent": 0,
                      "notes": null,
                      "template_version": "cotizacion/2026-10",
                      "presentation": {
                        "ejecutivo": {
                          "name": "Valentina Soto",
                          "phone": "+56987654321",
                          "email": "valentina.soto@inmobiliaria.example"
                        },
                        "sala": {
                          "name": "Sala Los Aromos",
                          "address": "Av. Vicuña Mackenna 9000, La Florida",
                          "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                        },
                        "image_url": null
                      },
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:05:00.000Z",
                      "sent_at": "2026-10-20T12:05:00.000Z",
                      "accepted_at": null,
                      "rejected_at": null,
                      "voided_at": null,
                      "voided_reason": null,
                      "is_expired": false
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    },
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Build a cotización draft",
        "description": "Builds a draft from unidades: a departamento or casa plus estacionamientos and bodegas, all of ONE proyecto and `disponible`. Prices come from the proyecto’s current published lista de precios (or `price_list_version_id`), which the cotización PINS: a newer list never rewrites it. Currency UF (default) or CLP; per-line and global discounts; validity (`valid_until` or `valid_days`, default 7); and the financing block (pie %, pie in cuotas, crédito hipotecario → dividendo and renta requerida; omitted → defaults, `null` → none). Only a name is needed for the buyer, never a RUT. 404 for a unidad or lead outside the caller’s proyectos; 409 `unit_not_available`, `project_has_no_price_list`; 400 `unit_not_priced`. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "The lead this offer is for. Gives the buyer’s name, contact, conversation and the ejecutivo."
                  },
                  "buyer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The buyer’s name (a name is enough, never a RUT). Defaults to the lead’s contact."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "unit_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The unidad (`GET /units`)."
                        },
                        "discount": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "percent",
                                "amount"
                              ],
                              "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "type",
                            "value"
                          ],
                          "additionalProperties": false,
                          "description": "A discount on this line only (applied before the global one)."
                        }
                      },
                      "required": [
                        "unit_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "The unidades offered: a departamento or casa plus estacionamientos and bodegas, all of ONE proyecto and `disponible`."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Default UF."
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Last valid day (YYYY-MM-DD). Default: today + valid_days."
                  },
                  "valid_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 365,
                    "description": "Validity in days from today (Chile). Default 7."
                  },
                  "discount": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "percent",
                          "amount"
                        ],
                        "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                      },
                      "value": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1000000000
                      }
                    },
                    "required": [
                      "type",
                      "value"
                    ],
                    "additionalProperties": false,
                    "description": "The global discount, applied after the line discounts."
                  },
                  "financing": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "pie_percent": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Pie as a percent of the total. Default 20."
                      },
                      "pie_installments": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 120,
                        "description": "Pie paid in this many monthly cuotas (1 = a single payment). Default 1."
                      },
                      "credit_years": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 40,
                        "description": "Crédito hipotecario term in years. Default 30."
                      },
                      "credit_rate_annual": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 30,
                        "description": "Crédito hipotecario annual rate in percent (4.5 = 4,5 %). Default 4.5."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The inmobiliaria financing block. Omitted → the defaults (pie 20 %, 1 cuota, 30 años, 4,5 %). `null` → no financing block."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "price_list_version_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "A published lista de precios version of the proyecto. Default: the proyecto’s current one. The cotización pins it."
                  }
                },
                "required": [
                  "lines"
                ],
                "additionalProperties": false
              },
              "example": {
                "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                "lines": [
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000401"
                  },
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000402"
                  },
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000403"
                  }
                ],
                "currency": "UF",
                "valid_days": 7,
                "discount": {
                  "type": "percent",
                  "value": 2
                },
                "financing": {
                  "pie_percent": 20,
                  "pie_installments": 36,
                  "credit_years": 30,
                  "credit_rate_annual": 4.5
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft built",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "display_id": {
                          "type": "string",
                          "description": "`CZ-12`; empty on a calculation."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "sent",
                            "accepted",
                            "rejected",
                            "voided"
                          ],
                          "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                        },
                        "buyer_name": {
                          "type": "string"
                        },
                        "lead_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "contact_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "owner_user_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The ejecutivo who answers for it."
                        },
                        "project_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "project_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "price_list_version_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The lista de precios version it was priced from (frozen)."
                        },
                        "price_list_version_number": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "price_list_superseded": {
                          "type": "boolean",
                          "description": "true: a newer list was published since. A draft must be re-priced before sending."
                        },
                        "currency": {
                          "type": "string",
                          "enum": [
                            "UF",
                            "CLP"
                          ],
                          "description": "The currency the offer is written in."
                        },
                        "uf_value_clp": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The UF used, frozen with the offer."
                        },
                        "uf_value_date": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "valid_until": {
                          "type": "string"
                        },
                        "discount_type": {
                          "type": "string",
                          "enum": [
                            "percent",
                            "amount"
                          ],
                          "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                        },
                        "discount_value": {
                          "type": "number"
                        },
                        "list_total": {
                          "type": "number"
                        },
                        "line_discount_total": {
                          "type": "number"
                        },
                        "global_discount_total": {
                          "type": "number"
                        },
                        "discount_total": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "discount_percent": {
                          "type": "number",
                          "description": "Effective discount over the list total, in percent."
                        },
                        "notes": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "template_version": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "presentation": {
                          "type": "object",
                          "properties": {
                            "ejecutivo": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "phone": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "email": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "phone",
                                "email"
                              ],
                              "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                            },
                            "sala": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "hours": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "address",
                                "hours"
                              ],
                              "description": "The proyecto’s sala de ventas, when it has one."
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The tipología’s floor plan (or render) printed on the PDF."
                            }
                          },
                          "required": [
                            "ejecutivo",
                            "sala",
                            "image_url"
                          ],
                          "description": "Who to call and where, printed on the PDF and frozen with the offer."
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_by_kind": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        },
                        "sent_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "rejected_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_expired": {
                          "type": "boolean"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "total_uf": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "total_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "position": {
                                "type": "integer"
                              },
                              "offering_type": {
                                "type": "string",
                                "description": "`property` today (a unidad)."
                              },
                              "offering_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "«Departamento 1203 · 3D2B 78 m²», as quoted."
                              },
                              "offering_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "detail": {
                                "type": "object",
                                "properties": {
                                  "model_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bedrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "bathrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "usable_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "terrace_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "floor": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "orientation": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "description": "What a buyer reads about the unidad, as it was when quoted."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "unit_price": {
                                "type": "number",
                                "description": "List price in the cotización’s currency."
                              },
                              "discount_type": {
                                "type": "string",
                                "enum": [
                                  "percent",
                                  "amount"
                                ],
                                "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                              },
                              "discount_value": {
                                "type": "number"
                              },
                              "list_total": {
                                "type": "number"
                              },
                              "discount_total": {
                                "type": "number"
                              },
                              "total": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "id",
                              "position",
                              "offering_type",
                              "offering_id",
                              "kind",
                              "label",
                              "offering_number",
                              "detail",
                              "quantity",
                              "unit_price",
                              "discount_type",
                              "discount_value",
                              "list_total",
                              "discount_total",
                              "total"
                            ]
                          }
                        },
                        "financing": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "pie_percent": {
                              "type": "number"
                            },
                            "pie_amount": {
                              "type": "number",
                              "description": "In the cotización’s currency."
                            },
                            "pie_installments": {
                              "type": "integer"
                            },
                            "pie_installment_amount": {
                              "type": "number"
                            },
                            "credit_amount": {
                              "type": "number"
                            },
                            "credit_years": {
                              "type": "integer"
                            },
                            "credit_rate_annual": {
                              "type": "number"
                            },
                            "monthly_payment": {
                              "type": "number",
                              "description": "The dividendo, in the cotización’s currency."
                            },
                            "monthly_payment_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "income_ratio": {
                              "type": "number",
                              "description": "Share of the renta the dividendo may take (0.25)."
                            },
                            "required_income_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Renta mensual requerida."
                            }
                          },
                          "required": [
                            "pie_percent",
                            "pie_amount",
                            "pie_installments",
                            "pie_installment_amount",
                            "credit_amount",
                            "credit_years",
                            "credit_rate_annual",
                            "monthly_payment",
                            "monthly_payment_clp",
                            "income_ratio",
                            "required_income_clp"
                          ],
                          "description": "The inmobiliaria block; null when the cotización has none."
                        },
                        "sends": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "channel": {
                                "type": "string",
                                "enum": [
                                  "whatsapp",
                                  "email"
                                ]
                              },
                              "recipient_hint": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Masked: +56•••••4567, a•••@correo.cl."
                              },
                              "sent_by": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_by_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "channel",
                              "recipient_hint",
                              "sent_by",
                              "sent_by_kind",
                              "sent_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "display_id",
                        "status",
                        "buyer_name",
                        "lead_id",
                        "contact_id",
                        "conversation_id",
                        "owner_user_id",
                        "project_id",
                        "project_name",
                        "price_list_version_id",
                        "price_list_version_number",
                        "price_list_superseded",
                        "currency",
                        "uf_value_clp",
                        "uf_value_date",
                        "valid_until",
                        "discount_type",
                        "discount_value",
                        "list_total",
                        "line_discount_total",
                        "global_discount_total",
                        "discount_total",
                        "total",
                        "discount_percent",
                        "notes",
                        "template_version",
                        "presentation",
                        "created_by",
                        "created_by_kind",
                        "created_at",
                        "updated_at",
                        "sent_at",
                        "accepted_at",
                        "rejected_at",
                        "voided_at",
                        "voided_reason",
                        "is_expired",
                        "id",
                        "total_uf",
                        "total_clp",
                        "lines",
                        "financing",
                        "sends"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000901",
                    "display_id": "CZ-12",
                    "status": "draft",
                    "buyer_name": "Camila Rojas",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": null,
                    "conversation_id": null,
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                    "price_list_version_number": 2,
                    "price_list_superseded": false,
                    "currency": "UF",
                    "uf_value_clp": 39532.12,
                    "uf_value_date": "2026-10-20",
                    "valid_until": "2026-10-27",
                    "discount_type": "percent",
                    "discount_value": 0,
                    "list_total": 3510,
                    "line_discount_total": 0,
                    "global_discount_total": 0,
                    "discount_total": 0,
                    "total": 3510,
                    "discount_percent": 0,
                    "notes": null,
                    "template_version": "cotizacion/2026-10",
                    "presentation": {
                      "ejecutivo": {
                        "name": "Valentina Soto",
                        "phone": "+56987654321",
                        "email": "valentina.soto@inmobiliaria.example"
                      },
                      "sala": {
                        "name": "Sala Los Aromos",
                        "address": "Av. Vicuña Mackenna 9000, La Florida",
                        "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                      },
                      "image_url": null
                    },
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:05:00.000Z",
                    "sent_at": null,
                    "accepted_at": null,
                    "rejected_at": null,
                    "voided_at": null,
                    "voided_reason": null,
                    "is_expired": false,
                    "total_uf": 3510,
                    "total_clp": 138760000,
                    "lines": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 0,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000401",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "offering_number": "1203",
                        "detail": {
                          "model_name": "3D2B 78 m²",
                          "bedrooms": 3,
                          "bathrooms": 2,
                          "usable_area_m2": 72.5,
                          "terrace_area_m2": 6,
                          "floor": 12,
                          "orientation": "norte"
                        },
                        "quantity": 1,
                        "unit_price": 3310,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 3310,
                        "discount_total": 0,
                        "total": 3310
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 1,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000402",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento 45",
                        "offering_number": "45",
                        "detail": {},
                        "quantity": 1,
                        "unit_price": 200,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 200,
                        "discount_total": 0,
                        "total": 200
                      }
                    ],
                    "financing": {
                      "pie_percent": 20,
                      "pie_amount": 702,
                      "pie_installments": 36,
                      "pie_installment_amount": 19.5,
                      "credit_amount": 2808,
                      "credit_years": 30,
                      "credit_rate_annual": 4.5,
                      "monthly_payment": 14.23,
                      "monthly_payment_clp": 562600,
                      "income_ratio": 0.25,
                      "required_income_clp": 2250400
                    },
                    "sends": []
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/cotizaciones/calculate": {
      "post": {
        "summary": "Calculate a cotización without saving it",
        "description": "Takes the same body as `POST /cotizaciones` and answers with the figures it would have (list total, discounts, total in UF and CLP, pie, dividendo, renta requerida) so a form can show them live. Saves nothing. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "The lead this offer is for. Gives the buyer’s name, contact, conversation and the ejecutivo."
                  },
                  "buyer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The buyer’s name (a name is enough, never a RUT). Defaults to the lead’s contact."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "unit_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The unidad (`GET /units`)."
                        },
                        "discount": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "percent",
                                "amount"
                              ],
                              "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "type",
                            "value"
                          ],
                          "additionalProperties": false,
                          "description": "A discount on this line only (applied before the global one)."
                        }
                      },
                      "required": [
                        "unit_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "The unidades offered: a departamento or casa plus estacionamientos and bodegas, all of ONE proyecto and `disponible`."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Default UF."
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Last valid day (YYYY-MM-DD). Default: today + valid_days."
                  },
                  "valid_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 365,
                    "description": "Validity in days from today (Chile). Default 7."
                  },
                  "discount": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "percent",
                          "amount"
                        ],
                        "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                      },
                      "value": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1000000000
                      }
                    },
                    "required": [
                      "type",
                      "value"
                    ],
                    "additionalProperties": false,
                    "description": "The global discount, applied after the line discounts."
                  },
                  "financing": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "pie_percent": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Pie as a percent of the total. Default 20."
                      },
                      "pie_installments": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 120,
                        "description": "Pie paid in this many monthly cuotas (1 = a single payment). Default 1."
                      },
                      "credit_years": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 40,
                        "description": "Crédito hipotecario term in years. Default 30."
                      },
                      "credit_rate_annual": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 30,
                        "description": "Crédito hipotecario annual rate in percent (4.5 = 4,5 %). Default 4.5."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The inmobiliaria financing block. Omitted → the defaults (pie 20 %, 1 cuota, 30 años, 4,5 %). `null` → no financing block."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "price_list_version_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "A published lista de precios version of the proyecto. Default: the proyecto’s current one. The cotización pins it."
                  }
                },
                "required": [
                  "lines"
                ],
                "additionalProperties": false
              },
              "example": {
                "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                "lines": [
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000401"
                  },
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000402"
                  },
                  {
                    "unit_id": "0199e3a0-0000-7000-8000-000000000403"
                  }
                ],
                "currency": "UF",
                "valid_days": 7,
                "discount": {
                  "type": "percent",
                  "value": 2
                },
                "financing": {
                  "pie_percent": 20,
                  "pie_installments": 36,
                  "credit_years": 30,
                  "credit_rate_annual": 4.5
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The calculation (id null, display_id empty, status draft)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "display_id": {
                          "type": "string",
                          "description": "`CZ-12`; empty on a calculation."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "sent",
                            "accepted",
                            "rejected",
                            "voided"
                          ],
                          "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                        },
                        "buyer_name": {
                          "type": "string"
                        },
                        "lead_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "contact_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "owner_user_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The ejecutivo who answers for it."
                        },
                        "project_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "project_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "price_list_version_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The lista de precios version it was priced from (frozen)."
                        },
                        "price_list_version_number": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "price_list_superseded": {
                          "type": "boolean",
                          "description": "true: a newer list was published since. A draft must be re-priced before sending."
                        },
                        "currency": {
                          "type": "string",
                          "enum": [
                            "UF",
                            "CLP"
                          ],
                          "description": "The currency the offer is written in."
                        },
                        "uf_value_clp": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The UF used, frozen with the offer."
                        },
                        "uf_value_date": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "valid_until": {
                          "type": "string"
                        },
                        "discount_type": {
                          "type": "string",
                          "enum": [
                            "percent",
                            "amount"
                          ],
                          "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                        },
                        "discount_value": {
                          "type": "number"
                        },
                        "list_total": {
                          "type": "number"
                        },
                        "line_discount_total": {
                          "type": "number"
                        },
                        "global_discount_total": {
                          "type": "number"
                        },
                        "discount_total": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "discount_percent": {
                          "type": "number",
                          "description": "Effective discount over the list total, in percent."
                        },
                        "notes": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "template_version": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "presentation": {
                          "type": "object",
                          "properties": {
                            "ejecutivo": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "phone": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "email": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "phone",
                                "email"
                              ],
                              "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                            },
                            "sala": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "hours": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "address",
                                "hours"
                              ],
                              "description": "The proyecto’s sala de ventas, when it has one."
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The tipología’s floor plan (or render) printed on the PDF."
                            }
                          },
                          "required": [
                            "ejecutivo",
                            "sala",
                            "image_url"
                          ],
                          "description": "Who to call and where, printed on the PDF and frozen with the offer."
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_by_kind": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        },
                        "sent_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "rejected_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_expired": {
                          "type": "boolean"
                        },
                        "id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "Null: a calculation is not saved."
                        },
                        "total_uf": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "total_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "position": {
                                "type": "integer"
                              },
                              "offering_type": {
                                "type": "string",
                                "description": "`property` today (a unidad)."
                              },
                              "offering_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "«Departamento 1203 · 3D2B 78 m²», as quoted."
                              },
                              "offering_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "detail": {
                                "type": "object",
                                "properties": {
                                  "model_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bedrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "bathrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "usable_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "terrace_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "floor": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "orientation": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "description": "What a buyer reads about the unidad, as it was when quoted."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "unit_price": {
                                "type": "number",
                                "description": "List price in the cotización’s currency."
                              },
                              "discount_type": {
                                "type": "string",
                                "enum": [
                                  "percent",
                                  "amount"
                                ],
                                "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                              },
                              "discount_value": {
                                "type": "number"
                              },
                              "list_total": {
                                "type": "number"
                              },
                              "discount_total": {
                                "type": "number"
                              },
                              "total": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "id",
                              "position",
                              "offering_type",
                              "offering_id",
                              "kind",
                              "label",
                              "offering_number",
                              "detail",
                              "quantity",
                              "unit_price",
                              "discount_type",
                              "discount_value",
                              "list_total",
                              "discount_total",
                              "total"
                            ]
                          }
                        },
                        "financing": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "pie_percent": {
                              "type": "number"
                            },
                            "pie_amount": {
                              "type": "number",
                              "description": "In the cotización’s currency."
                            },
                            "pie_installments": {
                              "type": "integer"
                            },
                            "pie_installment_amount": {
                              "type": "number"
                            },
                            "credit_amount": {
                              "type": "number"
                            },
                            "credit_years": {
                              "type": "integer"
                            },
                            "credit_rate_annual": {
                              "type": "number"
                            },
                            "monthly_payment": {
                              "type": "number",
                              "description": "The dividendo, in the cotización’s currency."
                            },
                            "monthly_payment_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "income_ratio": {
                              "type": "number",
                              "description": "Share of the renta the dividendo may take (0.25)."
                            },
                            "required_income_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Renta mensual requerida."
                            }
                          },
                          "required": [
                            "pie_percent",
                            "pie_amount",
                            "pie_installments",
                            "pie_installment_amount",
                            "credit_amount",
                            "credit_years",
                            "credit_rate_annual",
                            "monthly_payment",
                            "monthly_payment_clp",
                            "income_ratio",
                            "required_income_clp"
                          ],
                          "description": "The inmobiliaria block; null when the cotización has none."
                        },
                        "sends": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "channel": {
                                "type": "string",
                                "enum": [
                                  "whatsapp",
                                  "email"
                                ]
                              },
                              "recipient_hint": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Masked: +56•••••4567, a•••@correo.cl."
                              },
                              "sent_by": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_by_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "channel",
                              "recipient_hint",
                              "sent_by",
                              "sent_by_kind",
                              "sent_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "display_id",
                        "status",
                        "buyer_name",
                        "lead_id",
                        "contact_id",
                        "conversation_id",
                        "owner_user_id",
                        "project_id",
                        "project_name",
                        "price_list_version_id",
                        "price_list_version_number",
                        "price_list_superseded",
                        "currency",
                        "uf_value_clp",
                        "uf_value_date",
                        "valid_until",
                        "discount_type",
                        "discount_value",
                        "list_total",
                        "line_discount_total",
                        "global_discount_total",
                        "discount_total",
                        "total",
                        "discount_percent",
                        "notes",
                        "template_version",
                        "presentation",
                        "created_by",
                        "created_by_kind",
                        "created_at",
                        "updated_at",
                        "sent_at",
                        "accepted_at",
                        "rejected_at",
                        "voided_at",
                        "voided_reason",
                        "is_expired",
                        "id",
                        "total_uf",
                        "total_clp",
                        "lines",
                        "financing",
                        "sends"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": null,
                    "display_id": "",
                    "status": "draft",
                    "buyer_name": "Camila Rojas",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": null,
                    "conversation_id": null,
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                    "price_list_version_number": 2,
                    "price_list_superseded": false,
                    "currency": "UF",
                    "uf_value_clp": 39532.12,
                    "uf_value_date": "2026-10-20",
                    "valid_until": "2026-10-27",
                    "discount_type": "percent",
                    "discount_value": 0,
                    "list_total": 3510,
                    "line_discount_total": 0,
                    "global_discount_total": 0,
                    "discount_total": 0,
                    "total": 3510,
                    "discount_percent": 0,
                    "notes": null,
                    "template_version": "cotizacion/2026-10",
                    "presentation": {
                      "ejecutivo": {
                        "name": "Valentina Soto",
                        "phone": "+56987654321",
                        "email": "valentina.soto@inmobiliaria.example"
                      },
                      "sala": {
                        "name": "Sala Los Aromos",
                        "address": "Av. Vicuña Mackenna 9000, La Florida",
                        "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                      },
                      "image_url": null
                    },
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:05:00.000Z",
                    "sent_at": null,
                    "accepted_at": null,
                    "rejected_at": null,
                    "voided_at": null,
                    "voided_reason": null,
                    "is_expired": false,
                    "total_uf": 3510,
                    "total_clp": 138760000,
                    "lines": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 0,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000401",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "offering_number": "1203",
                        "detail": {
                          "model_name": "3D2B 78 m²",
                          "bedrooms": 3,
                          "bathrooms": 2,
                          "usable_area_m2": 72.5,
                          "terrace_area_m2": 6,
                          "floor": 12,
                          "orientation": "norte"
                        },
                        "quantity": 1,
                        "unit_price": 3310,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 3310,
                        "discount_total": 0,
                        "total": 3310
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 1,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000402",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento 45",
                        "offering_number": "45",
                        "detail": {},
                        "quantity": 1,
                        "unit_price": 200,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 200,
                        "discount_total": 0,
                        "total": 200
                      }
                    ],
                    "financing": {
                      "pie_percent": 20,
                      "pie_amount": 702,
                      "pie_installments": 36,
                      "pie_installment_amount": 19.5,
                      "credit_amount": 2808,
                      "credit_years": 30,
                      "credit_rate_annual": 4.5,
                      "monthly_payment": 14.23,
                      "monthly_payment_clp": 562600,
                      "income_ratio": 0.25,
                      "required_income_clp": 2250400
                    },
                    "sends": []
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/cotizaciones/{id}": {
      "get": {
        "summary": "Get a cotización",
        "description": "The offer with its lines, financing block and every send. `price_list_superseded` tells a draft it must be re-priced. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The cotización",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "display_id": {
                          "type": "string",
                          "description": "`CZ-12`; empty on a calculation."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "sent",
                            "accepted",
                            "rejected",
                            "voided"
                          ],
                          "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                        },
                        "buyer_name": {
                          "type": "string"
                        },
                        "lead_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "contact_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "owner_user_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The ejecutivo who answers for it."
                        },
                        "project_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "project_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "price_list_version_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The lista de precios version it was priced from (frozen)."
                        },
                        "price_list_version_number": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "price_list_superseded": {
                          "type": "boolean",
                          "description": "true: a newer list was published since. A draft must be re-priced before sending."
                        },
                        "currency": {
                          "type": "string",
                          "enum": [
                            "UF",
                            "CLP"
                          ],
                          "description": "The currency the offer is written in."
                        },
                        "uf_value_clp": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The UF used, frozen with the offer."
                        },
                        "uf_value_date": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "valid_until": {
                          "type": "string"
                        },
                        "discount_type": {
                          "type": "string",
                          "enum": [
                            "percent",
                            "amount"
                          ],
                          "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                        },
                        "discount_value": {
                          "type": "number"
                        },
                        "list_total": {
                          "type": "number"
                        },
                        "line_discount_total": {
                          "type": "number"
                        },
                        "global_discount_total": {
                          "type": "number"
                        },
                        "discount_total": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "discount_percent": {
                          "type": "number",
                          "description": "Effective discount over the list total, in percent."
                        },
                        "notes": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "template_version": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "presentation": {
                          "type": "object",
                          "properties": {
                            "ejecutivo": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "phone": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "email": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "phone",
                                "email"
                              ],
                              "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                            },
                            "sala": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "hours": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "address",
                                "hours"
                              ],
                              "description": "The proyecto’s sala de ventas, when it has one."
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The tipología’s floor plan (or render) printed on the PDF."
                            }
                          },
                          "required": [
                            "ejecutivo",
                            "sala",
                            "image_url"
                          ],
                          "description": "Who to call and where, printed on the PDF and frozen with the offer."
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_by_kind": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        },
                        "sent_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "rejected_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_expired": {
                          "type": "boolean"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "total_uf": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "total_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "position": {
                                "type": "integer"
                              },
                              "offering_type": {
                                "type": "string",
                                "description": "`property` today (a unidad)."
                              },
                              "offering_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "«Departamento 1203 · 3D2B 78 m²», as quoted."
                              },
                              "offering_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "detail": {
                                "type": "object",
                                "properties": {
                                  "model_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bedrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "bathrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "usable_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "terrace_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "floor": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "orientation": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "description": "What a buyer reads about the unidad, as it was when quoted."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "unit_price": {
                                "type": "number",
                                "description": "List price in the cotización’s currency."
                              },
                              "discount_type": {
                                "type": "string",
                                "enum": [
                                  "percent",
                                  "amount"
                                ],
                                "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                              },
                              "discount_value": {
                                "type": "number"
                              },
                              "list_total": {
                                "type": "number"
                              },
                              "discount_total": {
                                "type": "number"
                              },
                              "total": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "id",
                              "position",
                              "offering_type",
                              "offering_id",
                              "kind",
                              "label",
                              "offering_number",
                              "detail",
                              "quantity",
                              "unit_price",
                              "discount_type",
                              "discount_value",
                              "list_total",
                              "discount_total",
                              "total"
                            ]
                          }
                        },
                        "financing": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "pie_percent": {
                              "type": "number"
                            },
                            "pie_amount": {
                              "type": "number",
                              "description": "In the cotización’s currency."
                            },
                            "pie_installments": {
                              "type": "integer"
                            },
                            "pie_installment_amount": {
                              "type": "number"
                            },
                            "credit_amount": {
                              "type": "number"
                            },
                            "credit_years": {
                              "type": "integer"
                            },
                            "credit_rate_annual": {
                              "type": "number"
                            },
                            "monthly_payment": {
                              "type": "number",
                              "description": "The dividendo, in the cotización’s currency."
                            },
                            "monthly_payment_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "income_ratio": {
                              "type": "number",
                              "description": "Share of the renta the dividendo may take (0.25)."
                            },
                            "required_income_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Renta mensual requerida."
                            }
                          },
                          "required": [
                            "pie_percent",
                            "pie_amount",
                            "pie_installments",
                            "pie_installment_amount",
                            "credit_amount",
                            "credit_years",
                            "credit_rate_annual",
                            "monthly_payment",
                            "monthly_payment_clp",
                            "income_ratio",
                            "required_income_clp"
                          ],
                          "description": "The inmobiliaria block; null when the cotización has none."
                        },
                        "sends": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "channel": {
                                "type": "string",
                                "enum": [
                                  "whatsapp",
                                  "email"
                                ]
                              },
                              "recipient_hint": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Masked: +56•••••4567, a•••@correo.cl."
                              },
                              "sent_by": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_by_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "channel",
                              "recipient_hint",
                              "sent_by",
                              "sent_by_kind",
                              "sent_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "display_id",
                        "status",
                        "buyer_name",
                        "lead_id",
                        "contact_id",
                        "conversation_id",
                        "owner_user_id",
                        "project_id",
                        "project_name",
                        "price_list_version_id",
                        "price_list_version_number",
                        "price_list_superseded",
                        "currency",
                        "uf_value_clp",
                        "uf_value_date",
                        "valid_until",
                        "discount_type",
                        "discount_value",
                        "list_total",
                        "line_discount_total",
                        "global_discount_total",
                        "discount_total",
                        "total",
                        "discount_percent",
                        "notes",
                        "template_version",
                        "presentation",
                        "created_by",
                        "created_by_kind",
                        "created_at",
                        "updated_at",
                        "sent_at",
                        "accepted_at",
                        "rejected_at",
                        "voided_at",
                        "voided_reason",
                        "is_expired",
                        "id",
                        "total_uf",
                        "total_clp",
                        "lines",
                        "financing",
                        "sends"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000901",
                    "display_id": "CZ-12",
                    "status": "sent",
                    "buyer_name": "Camila Rojas",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": null,
                    "conversation_id": null,
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                    "price_list_version_number": 2,
                    "price_list_superseded": false,
                    "currency": "UF",
                    "uf_value_clp": 39532.12,
                    "uf_value_date": "2026-10-20",
                    "valid_until": "2026-10-27",
                    "discount_type": "percent",
                    "discount_value": 0,
                    "list_total": 3510,
                    "line_discount_total": 0,
                    "global_discount_total": 0,
                    "discount_total": 0,
                    "total": 3510,
                    "discount_percent": 0,
                    "notes": null,
                    "template_version": "cotizacion/2026-10",
                    "presentation": {
                      "ejecutivo": {
                        "name": "Valentina Soto",
                        "phone": "+56987654321",
                        "email": "valentina.soto@inmobiliaria.example"
                      },
                      "sala": {
                        "name": "Sala Los Aromos",
                        "address": "Av. Vicuña Mackenna 9000, La Florida",
                        "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                      },
                      "image_url": null
                    },
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:05:00.000Z",
                    "sent_at": "2026-10-20T12:05:00.000Z",
                    "accepted_at": null,
                    "rejected_at": null,
                    "voided_at": null,
                    "voided_reason": null,
                    "is_expired": false,
                    "total_uf": 3510,
                    "total_clp": 138760000,
                    "lines": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 0,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000401",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "offering_number": "1203",
                        "detail": {
                          "model_name": "3D2B 78 m²",
                          "bedrooms": 3,
                          "bathrooms": 2,
                          "usable_area_m2": 72.5,
                          "terrace_area_m2": 6,
                          "floor": 12,
                          "orientation": "norte"
                        },
                        "quantity": 1,
                        "unit_price": 3310,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 3310,
                        "discount_total": 0,
                        "total": 3310
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 1,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000402",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento 45",
                        "offering_number": "45",
                        "detail": {},
                        "quantity": 1,
                        "unit_price": 200,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 200,
                        "discount_total": 0,
                        "total": 200
                      }
                    ],
                    "financing": {
                      "pie_percent": 20,
                      "pie_amount": 702,
                      "pie_installments": 36,
                      "pie_installment_amount": 19.5,
                      "credit_amount": 2808,
                      "credit_years": 30,
                      "credit_rate_annual": 4.5,
                      "monthly_payment": 14.23,
                      "monthly_payment_clp": 562600,
                      "income_ratio": 0.25,
                      "required_income_clp": 2250400
                    },
                    "sends": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "channel": "whatsapp",
                        "recipient_hint": "+56•••••4567",
                        "sent_by": "0199e3a0-0000-7000-8000-0000000000bb",
                        "sent_by_kind": "ui_member",
                        "sent_at": "2026-10-20T12:05:00.000Z"
                      }
                    ]
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Discard a cotización draft",
        "description": "Drafts only: a sent cotización is a record and cannot be deleted (409 `cotizacion_not_draft`). Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Discarded",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000901",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a cotización draft",
        "description": "Drafts only (409 `cotizacion_not_draft` once sent). Any field of the create body; the figures are recomputed. A draft keeps the lista de precios version it pinned unless `refresh_prices: true` re-prices it on the proyecto’s current list. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lead_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "The lead this offer is for. Gives the buyer’s name, contact, conversation and the ejecutivo."
                  },
                  "buyer_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The buyer’s name (a name is enough, never a RUT). Defaults to the lead’s contact."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "unit_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The unidad (`GET /units`)."
                        },
                        "discount": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "percent",
                                "amount"
                              ],
                              "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                            },
                            "value": {
                              "type": "number",
                              "minimum": 0,
                              "maximum": 1000000000
                            }
                          },
                          "required": [
                            "type",
                            "value"
                          ],
                          "additionalProperties": false,
                          "description": "A discount on this line only (applied before the global one)."
                        }
                      },
                      "required": [
                        "unit_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 20,
                    "description": "The unidades offered: a departamento or casa plus estacionamientos and bodegas, all of ONE proyecto and `disponible`."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "UF",
                      "CLP"
                    ],
                    "description": "Default UF."
                  },
                  "valid_until": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Last valid day (YYYY-MM-DD). Default: today + valid_days."
                  },
                  "valid_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 365,
                    "description": "Validity in days from today (Chile). Default 7."
                  },
                  "discount": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "percent",
                          "amount"
                        ],
                        "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                      },
                      "value": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 1000000000
                      }
                    },
                    "required": [
                      "type",
                      "value"
                    ],
                    "additionalProperties": false,
                    "description": "The global discount, applied after the line discounts."
                  },
                  "financing": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "pie_percent": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 100,
                        "description": "Pie as a percent of the total. Default 20."
                      },
                      "pie_installments": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 120,
                        "description": "Pie paid in this many monthly cuotas (1 = a single payment). Default 1."
                      },
                      "credit_years": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 40,
                        "description": "Crédito hipotecario term in years. Default 30."
                      },
                      "credit_rate_annual": {
                        "type": "number",
                        "minimum": 0,
                        "maximum": 30,
                        "description": "Crédito hipotecario annual rate in percent (4.5 = 4,5 %). Default 4.5."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The inmobiliaria financing block. Omitted → the defaults (pie 20 %, 1 cuota, 30 años, 4,5 %). `null` → no financing block."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "price_list_version_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "A published lista de precios version of the proyecto. Default: the proyecto’s current one. The cotización pins it."
                  },
                  "refresh_prices": {
                    "type": "boolean",
                    "description": "true: re-price on the proyecto’s CURRENT lista de precios (a draft keeps the version it pinned otherwise)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "discount": {
                  "type": "percent",
                  "value": 3
                },
                "refresh_prices": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "display_id": {
                          "type": "string",
                          "description": "`CZ-12`; empty on a calculation."
                        },
                        "status": {
                          "type": "string",
                          "enum": [
                            "draft",
                            "sent",
                            "accepted",
                            "rejected",
                            "voided"
                          ],
                          "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                        },
                        "buyer_name": {
                          "type": "string"
                        },
                        "lead_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "contact_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "owner_user_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The ejecutivo who answers for it."
                        },
                        "project_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "project_name": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "price_list_version_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid",
                          "description": "The lista de precios version it was priced from (frozen)."
                        },
                        "price_list_version_number": {
                          "type": [
                            "integer",
                            "null"
                          ]
                        },
                        "price_list_superseded": {
                          "type": "boolean",
                          "description": "true: a newer list was published since. A draft must be re-priced before sending."
                        },
                        "currency": {
                          "type": "string",
                          "enum": [
                            "UF",
                            "CLP"
                          ],
                          "description": "The currency the offer is written in."
                        },
                        "uf_value_clp": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The UF used, frozen with the offer."
                        },
                        "uf_value_date": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "valid_until": {
                          "type": "string"
                        },
                        "discount_type": {
                          "type": "string",
                          "enum": [
                            "percent",
                            "amount"
                          ],
                          "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                        },
                        "discount_value": {
                          "type": "number"
                        },
                        "list_total": {
                          "type": "number"
                        },
                        "line_discount_total": {
                          "type": "number"
                        },
                        "global_discount_total": {
                          "type": "number"
                        },
                        "discount_total": {
                          "type": "number"
                        },
                        "total": {
                          "type": "number"
                        },
                        "discount_percent": {
                          "type": "number",
                          "description": "Effective discount over the list total, in percent."
                        },
                        "notes": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "template_version": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "presentation": {
                          "type": "object",
                          "properties": {
                            "ejecutivo": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "phone": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "email": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "phone",
                                "email"
                              ],
                              "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                            },
                            "sala": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "name": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "address": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                },
                                "hours": {
                                  "type": [
                                    "string",
                                    "null"
                                  ]
                                }
                              },
                              "required": [
                                "name",
                                "address",
                                "hours"
                              ],
                              "description": "The proyecto’s sala de ventas, when it has one."
                            },
                            "image_url": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "The tipología’s floor plan (or render) printed on the PDF."
                            }
                          },
                          "required": [
                            "ejecutivo",
                            "sala",
                            "image_url"
                          ],
                          "description": "Who to call and where, printed on the PDF and frozen with the offer."
                        },
                        "created_by": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_by_kind": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "created_at": {
                          "type": "string"
                        },
                        "updated_at": {
                          "type": "string"
                        },
                        "sent_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "accepted_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "rejected_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "voided_reason": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_expired": {
                          "type": "boolean"
                        },
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "total_uf": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "total_clp": {
                          "type": [
                            "number",
                            "null"
                          ]
                        },
                        "lines": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "position": {
                                "type": "integer"
                              },
                              "offering_type": {
                                "type": "string",
                                "description": "`property` today (a unidad)."
                              },
                              "offering_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "label": {
                                "type": "string",
                                "description": "«Departamento 1203 · 3D2B 78 m²», as quoted."
                              },
                              "offering_number": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "detail": {
                                "type": "object",
                                "properties": {
                                  "model_name": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "bedrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "bathrooms": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "usable_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "terrace_area_m2": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "floor": {
                                    "type": [
                                      "number",
                                      "null"
                                    ]
                                  },
                                  "orientation": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "description": "What a buyer reads about the unidad, as it was when quoted."
                              },
                              "quantity": {
                                "type": "integer"
                              },
                              "unit_price": {
                                "type": "number",
                                "description": "List price in the cotización’s currency."
                              },
                              "discount_type": {
                                "type": "string",
                                "enum": [
                                  "percent",
                                  "amount"
                                ],
                                "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                              },
                              "discount_value": {
                                "type": "number"
                              },
                              "list_total": {
                                "type": "number"
                              },
                              "discount_total": {
                                "type": "number"
                              },
                              "total": {
                                "type": "number"
                              }
                            },
                            "required": [
                              "id",
                              "position",
                              "offering_type",
                              "offering_id",
                              "kind",
                              "label",
                              "offering_number",
                              "detail",
                              "quantity",
                              "unit_price",
                              "discount_type",
                              "discount_value",
                              "list_total",
                              "discount_total",
                              "total"
                            ]
                          }
                        },
                        "financing": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "pie_percent": {
                              "type": "number"
                            },
                            "pie_amount": {
                              "type": "number",
                              "description": "In the cotización’s currency."
                            },
                            "pie_installments": {
                              "type": "integer"
                            },
                            "pie_installment_amount": {
                              "type": "number"
                            },
                            "credit_amount": {
                              "type": "number"
                            },
                            "credit_years": {
                              "type": "integer"
                            },
                            "credit_rate_annual": {
                              "type": "number"
                            },
                            "monthly_payment": {
                              "type": "number",
                              "description": "The dividendo, in the cotización’s currency."
                            },
                            "monthly_payment_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "income_ratio": {
                              "type": "number",
                              "description": "Share of the renta the dividendo may take (0.25)."
                            },
                            "required_income_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "Renta mensual requerida."
                            }
                          },
                          "required": [
                            "pie_percent",
                            "pie_amount",
                            "pie_installments",
                            "pie_installment_amount",
                            "credit_amount",
                            "credit_years",
                            "credit_rate_annual",
                            "monthly_payment",
                            "monthly_payment_clp",
                            "income_ratio",
                            "required_income_clp"
                          ],
                          "description": "The inmobiliaria block; null when the cotización has none."
                        },
                        "sends": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "channel": {
                                "type": "string",
                                "enum": [
                                  "whatsapp",
                                  "email"
                                ]
                              },
                              "recipient_hint": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Masked: +56•••••4567, a•••@correo.cl."
                              },
                              "sent_by": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_by_kind": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "sent_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "channel",
                              "recipient_hint",
                              "sent_by",
                              "sent_by_kind",
                              "sent_at"
                            ]
                          }
                        }
                      },
                      "required": [
                        "display_id",
                        "status",
                        "buyer_name",
                        "lead_id",
                        "contact_id",
                        "conversation_id",
                        "owner_user_id",
                        "project_id",
                        "project_name",
                        "price_list_version_id",
                        "price_list_version_number",
                        "price_list_superseded",
                        "currency",
                        "uf_value_clp",
                        "uf_value_date",
                        "valid_until",
                        "discount_type",
                        "discount_value",
                        "list_total",
                        "line_discount_total",
                        "global_discount_total",
                        "discount_total",
                        "total",
                        "discount_percent",
                        "notes",
                        "template_version",
                        "presentation",
                        "created_by",
                        "created_by_kind",
                        "created_at",
                        "updated_at",
                        "sent_at",
                        "accepted_at",
                        "rejected_at",
                        "voided_at",
                        "voided_reason",
                        "is_expired",
                        "id",
                        "total_uf",
                        "total_clp",
                        "lines",
                        "financing",
                        "sends"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "uf_value": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "clp": {
                              "type": "number"
                            },
                            "date": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "clp",
                            "date"
                          ]
                        }
                      },
                      "required": [
                        "uf_value"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000901",
                    "display_id": "CZ-12",
                    "status": "draft",
                    "buyer_name": "Camila Rojas",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": null,
                    "conversation_id": null,
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                    "price_list_version_number": 2,
                    "price_list_superseded": false,
                    "currency": "UF",
                    "uf_value_clp": 39532.12,
                    "uf_value_date": "2026-10-20",
                    "valid_until": "2026-10-27",
                    "discount_type": "percent",
                    "discount_value": 0,
                    "list_total": 3510,
                    "line_discount_total": 0,
                    "global_discount_total": 0,
                    "discount_total": 0,
                    "total": 3510,
                    "discount_percent": 0,
                    "notes": null,
                    "template_version": "cotizacion/2026-10",
                    "presentation": {
                      "ejecutivo": {
                        "name": "Valentina Soto",
                        "phone": "+56987654321",
                        "email": "valentina.soto@inmobiliaria.example"
                      },
                      "sala": {
                        "name": "Sala Los Aromos",
                        "address": "Av. Vicuña Mackenna 9000, La Florida",
                        "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                      },
                      "image_url": null
                    },
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:05:00.000Z",
                    "sent_at": null,
                    "accepted_at": null,
                    "rejected_at": null,
                    "voided_at": null,
                    "voided_reason": null,
                    "is_expired": false,
                    "total_uf": 3510,
                    "total_clp": 138760000,
                    "lines": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 0,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000401",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "offering_number": "1203",
                        "detail": {
                          "model_name": "3D2B 78 m²",
                          "bedrooms": 3,
                          "bathrooms": 2,
                          "usable_area_m2": 72.5,
                          "terrace_area_m2": 6,
                          "floor": 12,
                          "orientation": "norte"
                        },
                        "quantity": 1,
                        "unit_price": 3310,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 3310,
                        "discount_total": 0,
                        "total": 3310
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000902",
                        "position": 1,
                        "offering_type": "property",
                        "offering_id": "0199e3a0-0000-7000-8000-000000000402",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento 45",
                        "offering_number": "45",
                        "detail": {},
                        "quantity": 1,
                        "unit_price": 200,
                        "discount_type": "percent",
                        "discount_value": 0,
                        "list_total": 200,
                        "discount_total": 0,
                        "total": 200
                      }
                    ],
                    "financing": {
                      "pie_percent": 20,
                      "pie_amount": 702,
                      "pie_installments": 36,
                      "pie_installment_amount": 19.5,
                      "credit_amount": 2808,
                      "credit_years": 30,
                      "credit_rate_annual": 4.5,
                      "monthly_payment": 14.23,
                      "monthly_payment_clp": 562600,
                      "income_ratio": 0.25,
                      "required_income_clp": 2250400
                    },
                    "sends": []
                  },
                  "meta": {
                    "uf_value": {
                      "clp": 39532.12,
                      "date": "2026-10-20"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones/{id}/pdf": {
      "get": {
        "summary": "The cotización as a PDF",
        "description": "The branded PDF (the workspace’s logo and colour): lines, discounts, totals in UF and CLP, validity, and the financing block (pie, cuotas, crédito, dividendo, renta requerida). A sent cotización re-prints from its frozen figures; a draft prints marked «VISTA PREVIA». `Cache-Control: private, no-store`. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones/{id}/send": {
      "post": {
        "summary": "Send the cotización PDF by WhatsApp or email",
        "description": "Renders the PDF and delivers it: `whatsapp` on the buyer’s conversation (only inside Meta’s 24 h window: 409 `whatsapp_window_closed`, 409 `no_whatsapp_thread`) or `email` to the lead contact’s address (409 `no_email`). A draft is re-priced with today’s UF, delivered, and only then frozen as `sent`; a failed delivery leaves it a draft. A draft on a superseded lista de precios is a 409 `price_list_superseded` (re-price it first); an expired or voided one is a 409. A draft whose discount is above the sender’s role limit (`max_discount_percent`) is a 409 `discount_approval_required` until a jefe de sala or gerente approves it (`POST /cotizaciones/{id}/discount-approval`). A sent cotización can be re-sent as it was. Records who sent it and a masked recipient hint. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string",
                    "enum": [
                      "whatsapp",
                      "email"
                    ],
                    "description": "whatsapp: the buyer’s conversation, only inside Meta’s 24 h window (409 `whatsapp_window_closed` otherwise). email: the lead contact’s address."
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Replaces the default text that accompanies the PDF."
                  }
                },
                "required": [
                  "channel"
                ],
                "additionalProperties": false
              },
              "example": {
                "channel": "whatsapp"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delivered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "cotizacion": {
                          "type": "object",
                          "properties": {
                            "display_id": {
                              "type": "string",
                              "description": "`CZ-12`; empty on a calculation."
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "draft",
                                "sent",
                                "accepted",
                                "rejected",
                                "voided"
                              ],
                              "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                            },
                            "buyer_name": {
                              "type": "string"
                            },
                            "lead_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "contact_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "conversation_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "owner_user_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid",
                              "description": "The ejecutivo who answers for it."
                            },
                            "project_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "project_name": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "price_list_version_id": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid",
                              "description": "The lista de precios version it was priced from (frozen)."
                            },
                            "price_list_version_number": {
                              "type": [
                                "integer",
                                "null"
                              ]
                            },
                            "price_list_superseded": {
                              "type": "boolean",
                              "description": "true: a newer list was published since. A draft must be re-priced before sending."
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "UF",
                                "CLP"
                              ],
                              "description": "The currency the offer is written in."
                            },
                            "uf_value_clp": {
                              "type": [
                                "number",
                                "null"
                              ],
                              "description": "The UF used, frozen with the offer."
                            },
                            "uf_value_date": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "valid_until": {
                              "type": "string"
                            },
                            "discount_type": {
                              "type": "string",
                              "enum": [
                                "percent",
                                "amount"
                              ],
                              "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                            },
                            "discount_value": {
                              "type": "number"
                            },
                            "list_total": {
                              "type": "number"
                            },
                            "line_discount_total": {
                              "type": "number"
                            },
                            "global_discount_total": {
                              "type": "number"
                            },
                            "discount_total": {
                              "type": "number"
                            },
                            "total": {
                              "type": "number"
                            },
                            "discount_percent": {
                              "type": "number",
                              "description": "Effective discount over the list total, in percent."
                            },
                            "notes": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "template_version": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "presentation": {
                              "type": "object",
                              "properties": {
                                "ejecutivo": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "name": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "phone": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "email": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "name",
                                    "phone",
                                    "email"
                                  ],
                                  "description": "The ejecutivo to call: the lead’s owner, frozen with the offer."
                                },
                                "sala": {
                                  "type": [
                                    "object",
                                    "null"
                                  ],
                                  "properties": {
                                    "name": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "address": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    },
                                    "hours": {
                                      "type": [
                                        "string",
                                        "null"
                                      ]
                                    }
                                  },
                                  "required": [
                                    "name",
                                    "address",
                                    "hours"
                                  ],
                                  "description": "The proyecto’s sala de ventas, when it has one."
                                },
                                "image_url": {
                                  "type": [
                                    "string",
                                    "null"
                                  ],
                                  "description": "The tipología’s floor plan (or render) printed on the PDF."
                                }
                              },
                              "required": [
                                "ejecutivo",
                                "sala",
                                "image_url"
                              ],
                              "description": "Who to call and where, printed on the PDF and frozen with the offer."
                            },
                            "created_by": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "created_by_kind": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "created_at": {
                              "type": "string"
                            },
                            "updated_at": {
                              "type": "string"
                            },
                            "sent_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "accepted_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "rejected_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "voided_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "voided_reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "is_expired": {
                              "type": "boolean"
                            },
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "total_uf": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "total_clp": {
                              "type": [
                                "number",
                                "null"
                              ]
                            },
                            "lines": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "position": {
                                    "type": "integer"
                                  },
                                  "offering_type": {
                                    "type": "string",
                                    "description": "`property` today (a unidad)."
                                  },
                                  "offering_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "kind": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "label": {
                                    "type": "string",
                                    "description": "«Departamento 1203 · 3D2B 78 m²», as quoted."
                                  },
                                  "offering_number": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "detail": {
                                    "type": "object",
                                    "properties": {
                                      "model_name": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      },
                                      "bedrooms": {
                                        "type": [
                                          "number",
                                          "null"
                                        ]
                                      },
                                      "bathrooms": {
                                        "type": [
                                          "number",
                                          "null"
                                        ]
                                      },
                                      "usable_area_m2": {
                                        "type": [
                                          "number",
                                          "null"
                                        ]
                                      },
                                      "terrace_area_m2": {
                                        "type": [
                                          "number",
                                          "null"
                                        ]
                                      },
                                      "floor": {
                                        "type": [
                                          "number",
                                          "null"
                                        ]
                                      },
                                      "orientation": {
                                        "type": [
                                          "string",
                                          "null"
                                        ]
                                      }
                                    },
                                    "description": "What a buyer reads about the unidad, as it was when quoted."
                                  },
                                  "quantity": {
                                    "type": "integer"
                                  },
                                  "unit_price": {
                                    "type": "number",
                                    "description": "List price in the cotización’s currency."
                                  },
                                  "discount_type": {
                                    "type": "string",
                                    "enum": [
                                      "percent",
                                      "amount"
                                    ],
                                    "description": "percent: 0–100 of the base. amount: in the cotización’s currency."
                                  },
                                  "discount_value": {
                                    "type": "number"
                                  },
                                  "list_total": {
                                    "type": "number"
                                  },
                                  "discount_total": {
                                    "type": "number"
                                  },
                                  "total": {
                                    "type": "number"
                                  }
                                },
                                "required": [
                                  "id",
                                  "position",
                                  "offering_type",
                                  "offering_id",
                                  "kind",
                                  "label",
                                  "offering_number",
                                  "detail",
                                  "quantity",
                                  "unit_price",
                                  "discount_type",
                                  "discount_value",
                                  "list_total",
                                  "discount_total",
                                  "total"
                                ]
                              }
                            },
                            "financing": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "pie_percent": {
                                  "type": "number"
                                },
                                "pie_amount": {
                                  "type": "number",
                                  "description": "In the cotización’s currency."
                                },
                                "pie_installments": {
                                  "type": "integer"
                                },
                                "pie_installment_amount": {
                                  "type": "number"
                                },
                                "credit_amount": {
                                  "type": "number"
                                },
                                "credit_years": {
                                  "type": "integer"
                                },
                                "credit_rate_annual": {
                                  "type": "number"
                                },
                                "monthly_payment": {
                                  "type": "number",
                                  "description": "The dividendo, in the cotización’s currency."
                                },
                                "monthly_payment_clp": {
                                  "type": [
                                    "number",
                                    "null"
                                  ]
                                },
                                "income_ratio": {
                                  "type": "number",
                                  "description": "Share of the renta the dividendo may take (0.25)."
                                },
                                "required_income_clp": {
                                  "type": [
                                    "number",
                                    "null"
                                  ],
                                  "description": "Renta mensual requerida."
                                }
                              },
                              "required": [
                                "pie_percent",
                                "pie_amount",
                                "pie_installments",
                                "pie_installment_amount",
                                "credit_amount",
                                "credit_years",
                                "credit_rate_annual",
                                "monthly_payment",
                                "monthly_payment_clp",
                                "income_ratio",
                                "required_income_clp"
                              ],
                              "description": "The inmobiliaria block; null when the cotización has none."
                            },
                            "sends": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "channel": {
                                    "type": "string",
                                    "enum": [
                                      "whatsapp",
                                      "email"
                                    ]
                                  },
                                  "recipient_hint": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "Masked: +56•••••4567, a•••@correo.cl."
                                  },
                                  "sent_by": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "sent_by_kind": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "sent_at": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "id",
                                  "channel",
                                  "recipient_hint",
                                  "sent_by",
                                  "sent_by_kind",
                                  "sent_at"
                                ]
                              }
                            }
                          },
                          "required": [
                            "display_id",
                            "status",
                            "buyer_name",
                            "lead_id",
                            "contact_id",
                            "conversation_id",
                            "owner_user_id",
                            "project_id",
                            "project_name",
                            "price_list_version_id",
                            "price_list_version_number",
                            "price_list_superseded",
                            "currency",
                            "uf_value_clp",
                            "uf_value_date",
                            "valid_until",
                            "discount_type",
                            "discount_value",
                            "list_total",
                            "line_discount_total",
                            "global_discount_total",
                            "discount_total",
                            "total",
                            "discount_percent",
                            "notes",
                            "template_version",
                            "presentation",
                            "created_by",
                            "created_by_kind",
                            "created_at",
                            "updated_at",
                            "sent_at",
                            "accepted_at",
                            "rejected_at",
                            "voided_at",
                            "voided_reason",
                            "is_expired",
                            "id",
                            "total_uf",
                            "total_clp",
                            "lines",
                            "financing",
                            "sends"
                          ]
                        },
                        "channel": {
                          "type": "string",
                          "enum": [
                            "whatsapp",
                            "email"
                          ]
                        },
                        "recipient_hint": {
                          "type": "string"
                        },
                        "sent_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "cotizacion",
                        "channel",
                        "recipient_hint",
                        "sent_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "cotizacion": {
                      "id": "0199e3a0-0000-7000-8000-000000000901",
                      "display_id": "CZ-12",
                      "status": "sent",
                      "buyer_name": "Camila Rojas",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": null,
                      "conversation_id": null,
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "price_list_version_id": "0199e3a0-0000-7000-8000-000000000501",
                      "price_list_version_number": 2,
                      "price_list_superseded": false,
                      "currency": "UF",
                      "uf_value_clp": 39532.12,
                      "uf_value_date": "2026-10-20",
                      "valid_until": "2026-10-27",
                      "discount_type": "percent",
                      "discount_value": 0,
                      "list_total": 3510,
                      "line_discount_total": 0,
                      "global_discount_total": 0,
                      "discount_total": 0,
                      "total": 3510,
                      "discount_percent": 0,
                      "notes": null,
                      "template_version": "cotizacion/2026-10",
                      "presentation": {
                        "ejecutivo": {
                          "name": "Valentina Soto",
                          "phone": "+56987654321",
                          "email": "valentina.soto@inmobiliaria.example"
                        },
                        "sala": {
                          "name": "Sala Los Aromos",
                          "address": "Av. Vicuña Mackenna 9000, La Florida",
                          "hours": "Lunes a viernes 10:00–19:00 h · Sábados 10:00–14:00 h"
                        },
                        "image_url": null
                      },
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:05:00.000Z",
                      "sent_at": "2026-10-20T12:05:00.000Z",
                      "accepted_at": null,
                      "rejected_at": null,
                      "voided_at": null,
                      "voided_reason": null,
                      "is_expired": false,
                      "total_uf": 3510,
                      "total_clp": 138760000,
                      "lines": [
                        {
                          "id": "0199e3a0-0000-7000-8000-000000000902",
                          "position": 0,
                          "offering_type": "property",
                          "offering_id": "0199e3a0-0000-7000-8000-000000000401",
                          "kind": "departamento",
                          "label": "Departamento 1203 · 3D2B 78 m²",
                          "offering_number": "1203",
                          "detail": {
                            "model_name": "3D2B 78 m²",
                            "bedrooms": 3,
                            "bathrooms": 2,
                            "usable_area_m2": 72.5,
                            "terrace_area_m2": 6,
                            "floor": 12,
                            "orientation": "norte"
                          },
                          "quantity": 1,
                          "unit_price": 3310,
                          "discount_type": "percent",
                          "discount_value": 0,
                          "list_total": 3310,
                          "discount_total": 0,
                          "total": 3310
                        },
                        {
                          "id": "0199e3a0-0000-7000-8000-000000000902",
                          "position": 1,
                          "offering_type": "property",
                          "offering_id": "0199e3a0-0000-7000-8000-000000000402",
                          "kind": "estacionamiento",
                          "label": "Estacionamiento 45",
                          "offering_number": "45",
                          "detail": {},
                          "quantity": 1,
                          "unit_price": 200,
                          "discount_type": "percent",
                          "discount_value": 0,
                          "list_total": 200,
                          "discount_total": 0,
                          "total": 200
                        }
                      ],
                      "financing": {
                        "pie_percent": 20,
                        "pie_amount": 702,
                        "pie_installments": 36,
                        "pie_installment_amount": 19.5,
                        "credit_amount": 2808,
                        "credit_years": 30,
                        "credit_rate_annual": 4.5,
                        "monthly_payment": 14.23,
                        "monthly_payment_clp": 562600,
                        "income_ratio": 0.25,
                        "required_income_clp": 2250400
                      },
                      "sends": [
                        {
                          "id": "0199e3a0-0000-7000-8000-000000000902",
                          "channel": "whatsapp",
                          "recipient_hint": "+56•••••4567",
                          "sent_by": "0199e3a0-0000-7000-8000-0000000000bb",
                          "sent_by_kind": "ui_member",
                          "sent_at": "2026-10-20T12:05:00.000Z"
                        }
                      ]
                    },
                    "channel": "whatsapp",
                    "recipient_hint": "+56•••••4567",
                    "sent_at": "2026-10-20T12:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/real-estate-reports/projects": {
      "get": {
        "summary": "Developer report per proyecto",
        "description": "For an inmobiliaria: per proyecto and in total, the funnel by milestone (leads that arrived in the period, how many reached each step), the stock by status, absorption and sales velocity (reservas per month, months to sell out), desistimientos by reason, and an ejecutivo ranking. Filters: `project_id`, `sales_office_location_id` (a sala de ventas), `from` / `to`. A caller whose role narrows proyectos sees only the figures of their proyectos. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. Requires `marketplace:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "First day of the period, inclusive. Defaults to 29 days before `to`."
            },
            "required": false,
            "description": "First day of the period, inclusive. Defaults to 29 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\\d|3[01])$",
              "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock."
            },
            "required": false,
            "description": "Last day of the period, INCLUSIVE. Defaults to today in the workspace's clock.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only this proyecto (404 when it is missing or outside the caller’s proyectos)."
            },
            "required": false,
            "description": "Only this proyecto (404 when it is missing or outside the caller’s proyectos).",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only the proyectos this sala de ventas serves (a `location` id)."
            },
            "required": false,
            "description": "Only the proyectos this sala de ventas serves (a `location` id).",
            "name": "sales_office_location_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The developer board to read. Defaults to the workspace’s Proyectos board."
            },
            "required": false,
            "description": "The developer board to read. Defaults to the workspace’s Proyectos board.",
            "name": "pipeline_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DeveloperReport"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "pipeline_id": "0199e3a0-0000-7000-8000-000000000301",
                    "period": {
                      "from": "2026-09-21",
                      "to": "2026-10-20",
                      "timezone": "America/Santiago"
                    },
                    "filters": {
                      "project_id": null,
                      "sales_office_id": null
                    },
                    "totals": {
                      "funnel": {
                        "cohort": 120,
                        "steps": [
                          {
                            "key": "lead",
                            "name": "Lead",
                            "reached": 120,
                            "conversion_from_previous": null
                          },
                          {
                            "key": "visit_scheduled",
                            "name": "Visita agendada",
                            "reached": 54,
                            "conversion_from_previous": 0.45
                          }
                        ],
                        "lost": 18
                      },
                      "stock": {
                        "by_status": {
                          "disponible": 140,
                          "bloqueada": 2,
                          "reservada": 9,
                          "promesada": 12,
                          "vendida": 37,
                          "pausada": 0
                        },
                        "dwellings": 200,
                        "extras_by_status": {
                          "disponible": 80,
                          "bloqueada": 0,
                          "reservada": 3,
                          "promesada": 4,
                          "vendida": 13,
                          "pausada": 0
                        },
                        "extras": 100,
                        "available_value_uf": 512340.5
                      },
                      "absorption": {
                        "on_sale": 200,
                        "committed": 58,
                        "absorption_rate": 0.29,
                        "sold_rate": 0.185,
                        "reservations": 11,
                        "reservations_per_month": 11.2,
                        "months_to_sell_out": 12.5,
                        "by_month": [
                          {
                            "month": "2026-10",
                            "reservations": 11
                          }
                        ]
                      },
                      "desistimientos": {
                        "total": 4,
                        "by_reason": [
                          {
                            "reason": "financing_rejected",
                            "count": 3
                          },
                          {
                            "reason": "price",
                            "count": 1
                          }
                        ]
                      }
                    },
                    "projects": [],
                    "ejecutivos": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/reserva-terms": {
      "get": {
        "summary": "Get a proyecto’s reserva terms",
        "description": "What a reserva of this proyecto costs (`amount` in `currency`, UF or CLP; null = stated on each reserva) and how long it holds the unidades bloqueada (`hold_hours`, default 48). Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Reserva terms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ReservaTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "amount": 20,
                    "currency": "UF",
                    "hold_hours": 48
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set a proyecto’s reserva terms",
        "description": "Sets the default reserva amount (UF or CLP; both null to state it on each reserva) and the hold in hours (1–720). Applies to reservas started afterwards. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ReservaTerms"
                  },
                  {
                    "additionalProperties": false
                  }
                ]
              },
              "example": {
                "amount": 20,
                "currency": "UF",
                "hold_hours": 48
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ReservaTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "amount": 20,
                    "currency": "UF",
                    "hold_hours": 48
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones/{id}/reserva": {
      "post": {
        "summary": "Reserve a cotización’s unidades",
        "description": "From a SENT, still-valid cotización: every unidad on it becomes `bloqueada` until the deadline (the proyecto’s `hold_hours`, or `hold_hours`), and the buyer owes the reserva (`amount` + `currency`, else the proyecto’s reserva terms; a UF amount is charged in CLP at today’s UF). The payment is offered by the workspace’s charging preference for a reserva: a payment link (the connected provider, expiring with the hold) and/or the bank transfer, which the transfer matcher settles from the buyer’s comprobante. With `deliver` (default) the message goes to the buyer’s conversation as the teammate (inside the channel window; otherwise `delivery.status: not_sent` and the `text` to copy). When the reserva is fully paid the unidades become `reservada`, the cotización `accepted` and a negocio is opened (`GET /negocios`); the ejecutivo is notified. Unpaid at the deadline, the unidades return to `disponible`. 409: `unit_not_available` (another buyer holds one: nothing is blocked), `reserva_pending`, `cotizacion_not_reservable`, `cotizacion_expired`, `no_contact`, `uf_unavailable`; 400 `reserva_amount_required`. Requires `leads:write` (+ `messages:send` and `conversations:write` to deliver). Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1000000000,
                    "description": "The reserva amount, with `currency`. Omitted → the proyecto’s reserva terms (`PUT /projects/{id}/reserva-terms`)."
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "UF",
                      "CLP"
                    ]
                  },
                  "hold_hours": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 720,
                    "description": "Overrides the proyecto’s hold, in hours."
                  },
                  "co_buyer_contact_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 5,
                    "description": "Contacts buying jointly with the cotización’s buyer."
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The thread to send the payment message on. Default: the cotización’s conversation, else the buyer’s newest."
                  },
                  "deliver": {
                    "type": "boolean",
                    "description": "Send the payment message to the buyer now (default true). Needs `messages:send` and `conversations:write`; `false` only returns the text and the link to copy."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "amount": 20,
                "currency": "UF"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Reserva started",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "reserva": {
                          "$ref": "#/components/schemas/Reserva"
                        },
                        "payment": {
                          "$ref": "#/components/schemas/ReservaPayment"
                        },
                        "text": {
                          "type": "string",
                          "description": "The message for the buyer."
                        },
                        "delivery": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "sent",
                                "retrying",
                                "pending_approval",
                                "not_sent"
                              ]
                            },
                            "reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "approval_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "conversation_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "channel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "status",
                            "reason",
                            "message_id",
                            "conversation_id",
                            "channel"
                          ]
                        }
                      },
                      "required": [
                        "reserva",
                        "payment",
                        "text",
                        "delivery"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "pending",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": null,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": null,
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null,
                      "paid_clp": 0,
                      "outstanding_clp": 790642,
                      "payment_state": "pending",
                      "is_overdue": false
                    },
                    "payment": {
                      "mode": "mp_first",
                      "link": {
                        "checkout_id": "0199e3a0-0000-7000-8000-000000000d01",
                        "checkout_url": "https://api.vitrinadev.com/p/0199e3a0-0000-7000-8000-000000000d01",
                        "amount_clp": 790642
                      },
                      "transfer_accounts": [
                        {
                          "alias": "Cuenta corriente",
                          "bank": "Banco de Chile",
                          "account_type": "corriente",
                          "account_number": "00123456789",
                          "holder_name": "Inmobiliaria Los Aromos SpA",
                          "holder_rut": "76.123.456-7"
                        }
                      ]
                    },
                    "text": "Hola Camila, dejamos bloqueada Departamento 1203 · 3D2B 78 m² y Estacionamiento E-12 de Parque Los Aromos para ti hasta el 22/10 a las 12:00.\nPara confirmar la reserva, paga $790.642 antes de esa hora.\n\nPaga aquí: https://api.vitrinadev.com/p/…",
                    "delivery": {
                      "status": "sent",
                      "reason": null,
                      "message_id": "0199e3a0-0000-7000-8000-000000000f01",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "channel": "whatsapp"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservas": {
      "get": {
        "summary": "List reservas",
        "description": "Newest first, with what has been paid. Filters: `project_id`, `status`, `lead_id`, `cotizacion_id`. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos."
            },
            "required": false,
            "description": "Only these proyectos.",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pending",
                  "paid",
                  "expired",
                  "cancelled"
                ],
                "description": "pending: the unidades are bloqueada until `expires_at` while the buyer pays. paid: the negocio is open (`negocio_id`). expired: the deadline passed unpaid and the unidades are disponible again. cancelled: a person released them."
              },
              "maxItems": 50
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "cotizacion_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Default 50."
            },
            "required": false,
            "description": "Default 50.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Reservas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Reserva"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "pending",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": null,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": null,
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null,
                      "paid_clp": 0,
                      "outstanding_clp": 790642,
                      "payment_state": "pending",
                      "is_overdue": false
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservas/{id}": {
      "get": {
        "summary": "Get a reserva",
        "description": "One reserva: the unidades it holds, the deadline, the amount, what is paid and what is left, and the negocio it opened once paid. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Reserva",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reserva"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a01",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "cotizacion_display_id": "CZ-12",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "co_buyer_contact_ids": [],
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "property_ids": [
                      "0199e3a0-0000-7000-8000-000000000401",
                      "0199e3a0-0000-7000-8000-000000000402"
                    ],
                    "status": "pending",
                    "amount": 20,
                    "currency": "UF",
                    "amount_clp": 790642,
                    "uf_value_clp": 39532.12,
                    "expires_at": "2026-10-22T15:00:00.000Z",
                    "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                    "negocio_id": null,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T15:00:00.000Z",
                    "updated_at": "2026-10-20T15:00:00.000Z",
                    "paid_at": null,
                    "expired_at": null,
                    "cancelled_at": null,
                    "cancelled_reason": null,
                    "paid_clp": 0,
                    "outstanding_clp": 790642,
                    "payment_state": "pending",
                    "is_overdue": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservas/{id}/payment": {
      "get": {
        "summary": "How a reserva can be paid",
        "description": "For a pending reserva: the payment link (created on demand, expiring with the hold) and/or the transfer accounts, by the workspace’s charging preference. 409 `reserva_not_pending` otherwise. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment options",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "reserva": {
                          "$ref": "#/components/schemas/Reserva"
                        },
                        "payment": {
                          "$ref": "#/components/schemas/ReservaPayment"
                        }
                      },
                      "required": [
                        "reserva",
                        "payment"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "pending",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": null,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": null,
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null,
                      "paid_clp": 0,
                      "outstanding_clp": 790642,
                      "payment_state": "pending",
                      "is_overdue": false
                    },
                    "payment": {
                      "mode": "mp_first",
                      "link": {
                        "checkout_id": "0199e3a0-0000-7000-8000-000000000d01",
                        "checkout_url": "https://api.vitrinadev.com/p/0199e3a0-0000-7000-8000-000000000d01",
                        "amount_clp": 790642
                      },
                      "transfer_accounts": [
                        {
                          "alias": "Cuenta corriente",
                          "bank": "Banco de Chile",
                          "account_type": "corriente",
                          "account_number": "00123456789",
                          "holder_name": "Inmobiliaria Los Aromos SpA",
                          "holder_rut": "76.123.456-7"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/reservas/{id}/cancel": {
      "post": {
        "summary": "Release a reserva’s hold",
        "description": "A pending reserva only: the unidades return to `disponible`, the cobro is cancelled (money already paid stays on it for the business to return or apply) and the open payment link stops taking money. 409 `reserva_not_pending`, `reserva_already_paid`. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Why the hold is released (kept on the reserva)."
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El comprador desistió antes de pagar"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Released",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Reserva"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a01",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "cotizacion_display_id": "CZ-12",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "co_buyer_contact_ids": [],
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "property_ids": [
                      "0199e3a0-0000-7000-8000-000000000401",
                      "0199e3a0-0000-7000-8000-000000000402"
                    ],
                    "status": "cancelled",
                    "amount": 20,
                    "currency": "UF",
                    "amount_clp": 790642,
                    "uf_value_clp": 39532.12,
                    "expires_at": "2026-10-22T15:00:00.000Z",
                    "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                    "negocio_id": null,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_by_kind": "ui_member",
                    "created_at": "2026-10-20T15:00:00.000Z",
                    "updated_at": "2026-10-20T15:00:00.000Z",
                    "paid_at": null,
                    "expired_at": null,
                    "cancelled_at": "2026-10-21T10:00:00.000Z",
                    "cancelled_reason": "El comprador desistió antes de pagar",
                    "paid_clp": 0,
                    "outstanding_clp": 0,
                    "payment_state": "cancelled",
                    "is_overdue": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/negocios": {
      "get": {
        "summary": "List negocios",
        "description": "An inmobiliaria’s sales from reserva to entrega, newest reserva first. Filters: `project_id`, `stage`, `lead_id`, `contact_id`. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50,
              "description": "Only these proyectos."
            },
            "required": false,
            "description": "Only these proyectos.",
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "reservado",
                  "promesado",
                  "escriturado",
                  "entregado",
                  "desistido"
                ],
                "description": "Where the sale stands: reservado (the reserva is paid) → promesado → escriturado → entregado, or desistido."
              },
              "maxItems": 50
            },
            "required": false,
            "name": "stage",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Default 50."
            },
            "required": false,
            "description": "Default 50.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Negocios",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NegocioSummary"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000b01",
                      "display_id": "NG-3",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "buyer_name": "Camila Rojas",
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "stage": "reservado",
                      "currency": "UF",
                      "total": 3510,
                      "uf_value_clp": 39532.12,
                      "reserved_at": "2026-10-21T13:10:00.000Z",
                      "created_at": "2026-10-21T13:10:00.000Z",
                      "updated_at": "2026-10-21T13:10:00.000Z",
                      "promesa_due_on": "2026-11-20",
                      "promesa_signed_on": null,
                      "escritura_due_on": null,
                      "escritura_signed_on": null,
                      "escritura_notary": null,
                      "entrega_due_on": null,
                      "entregado_on": null,
                      "desisted_on": null,
                      "desist_reason": null,
                      "desist_money_outcome": null,
                      "desist_notes": null,
                      "milestone_alerted_key": null,
                      "next_deadline": {
                        "milestone": "promesa",
                        "due_on": "2026-11-20",
                        "overdue": false
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}": {
      "get": {
        "summary": "Get a negocio",
        "description": "One negocio: the buyer(s), the unidades at the accepted cotización’s prices (with their current status), the stage and the reserva that opened it. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Negocio",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "reservado",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": null,
                    "escritura_due_on": null,
                    "escritura_signed_on": null,
                    "escritura_notary": null,
                    "entrega_due_on": null,
                    "entregado_on": null,
                    "desisted_on": null,
                    "desist_reason": null,
                    "desist_money_outcome": null,
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": {
                      "milestone": "promesa",
                      "due_on": "2026-11-20",
                      "overdue": false
                    },
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/milestone-terms": {
      "get": {
        "summary": "Get a proyecto’s milestone deadlines",
        "description": "The default deadlines a new negocio of this proyecto gets: the promesa `promesa_due_days` after the reserva (default 30), the escritura `escritura_due_days` after the promesa and the entrega `entrega_due_days` after the escritura (null = no default). Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Milestone terms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioMilestoneTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "promesa_due_days": 30,
                    "escritura_due_days": 180,
                    "entrega_due_days": 60
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set a proyecto’s milestone deadlines",
        "description": "Applies to deadlines set afterwards (a new negocio, a promesa or escritura recorded later); existing deadlines are not moved. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/NegocioMilestoneTerms"
                  },
                  {
                    "additionalProperties": false
                  }
                ]
              },
              "example": {
                "promesa_due_days": 30,
                "escritura_due_days": 180,
                "entrega_due_days": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioMilestoneTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "promesa_due_days": 30,
                    "escritura_due_days": 180,
                    "entrega_due_days": 60
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/promesa": {
      "post": {
        "summary": "Record the promesa",
        "description": "A `reservado` negocio only (409 `negocio_stage_conflict` otherwise): the stage becomes `promesado`, the unidades `promesada`, and the escritura deadline is set (`escritura_due_on`, else `signed_on` + the proyecto’s `escritura_due_days`). Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signed_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Default today (Chile)."
                  },
                  "escritura_due_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Overrides the escritura deadline the proyecto would set."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "signed_on": "2026-11-10"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The negocio after the milestone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "promesado",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": "2026-11-10",
                    "escritura_due_on": "2027-05-09",
                    "escritura_signed_on": null,
                    "escritura_notary": null,
                    "entrega_due_on": null,
                    "entregado_on": null,
                    "desisted_on": null,
                    "desist_reason": null,
                    "desist_money_outcome": null,
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": {
                      "milestone": "escritura",
                      "due_on": "2027-05-09",
                      "overdue": false
                    },
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/escritura": {
      "post": {
        "summary": "Record the escritura",
        "description": "A `promesado` negocio only: the stage becomes `escriturado` and the unidades `vendida`. The escritura is signed at the notary (`notary`), never e-signed. Sets the entrega deadline (`entrega_due_on`, else + the proyecto’s `entrega_due_days`). Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "signed_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Default today (Chile)."
                  },
                  "notary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "entrega_due_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "signed_on": "2027-04-30",
                "notary": "Notaría Pérez, Santiago"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The negocio after the milestone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "escriturado",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": null,
                    "escritura_due_on": null,
                    "escritura_signed_on": "2027-04-30",
                    "escritura_notary": "Notaría Pérez, Santiago",
                    "entrega_due_on": null,
                    "entregado_on": null,
                    "desisted_on": null,
                    "desist_reason": null,
                    "desist_money_outcome": null,
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": {
                      "milestone": "promesa",
                      "due_on": "2026-11-20",
                      "overdue": false
                    },
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/entrega": {
      "post": {
        "summary": "Record the entrega",
        "description": "An `escriturado` negocio only: the stage becomes `entregado` (post-venta starts). Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "delivered_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Default today (Chile)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "delivered_on": "2027-06-15"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The negocio after the milestone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "entregado",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": null,
                    "escritura_due_on": null,
                    "escritura_signed_on": null,
                    "escritura_notary": null,
                    "entrega_due_on": null,
                    "entregado_on": "2027-06-15",
                    "desisted_on": null,
                    "desist_reason": null,
                    "desist_money_outcome": null,
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": null,
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/desistimiento": {
      "post": {
        "summary": "Record a desistimiento",
        "description": "Before the escritura only (`reservado` or `promesado`): the stage becomes `desistido`, the unidades return to `disponible` and the negocio stops holding them. Records the reason and the outcome for the money paid (`returned` | `retained` | `applied`), stamped on the reserva cobro as its disposition (devuelto | perdido | aplicado). Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "money_outcome": {
                    "type": "string",
                    "enum": [
                      "returned",
                      "retained",
                      "applied"
                    ],
                    "description": "What happens to the money the buyer paid: returned (devuelto), retained (retenido) or applied (aplicado to another negocio)."
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "desisted_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Default today (Chile)."
                  }
                },
                "required": [
                  "reason",
                  "money_outcome"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "No obtuvo el crédito hipotecario",
                "money_outcome": "returned"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The negocio after the milestone",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "desistido",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": null,
                    "escritura_due_on": null,
                    "escritura_signed_on": null,
                    "escritura_notary": null,
                    "entrega_due_on": null,
                    "entregado_on": null,
                    "desisted_on": "2026-12-02",
                    "desist_reason": "No obtuvo el crédito hipotecario",
                    "desist_money_outcome": "returned",
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": null,
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/negocios/{id}/deadlines": {
      "patch": {
        "summary": "Edit a negocio’s deadlines",
        "description": "Moves the promesa, escritura or entrega deadline (null clears it) and the escritura’s notaría. A moved deadline alerts again if it passes. 409 `negocio_closed` for a desistido or entregado negocio. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ reservas and negocios; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "promesa_due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "escritura_due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "entrega_due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "escritura_notary": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "promesa_due_on": "2026-11-30"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Negocio"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Parque Los Aromos",
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "reserva_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                    "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                    "buyer_name": "Camila Rojas",
                    "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                    "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "stage": "reservado",
                    "currency": "UF",
                    "total": 3510,
                    "uf_value_clp": 39532.12,
                    "reserved_at": "2026-10-21T13:10:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-21T13:10:00.000Z",
                    "promesa_due_on": "2026-11-20",
                    "promesa_signed_on": null,
                    "escritura_due_on": null,
                    "escritura_signed_on": null,
                    "escritura_notary": null,
                    "entrega_due_on": null,
                    "entregado_on": null,
                    "desisted_on": null,
                    "desist_reason": null,
                    "desist_money_outcome": null,
                    "desist_notes": null,
                    "milestone_alerted_key": null,
                    "next_deadline": {
                      "milestone": "promesa",
                      "due_on": "2026-11-20",
                      "overdue": false
                    },
                    "buyers": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b11",
                        "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                        "contact_name": "Camila Rojas",
                        "role": "titular",
                        "position": 0
                      }
                    ],
                    "units": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b21",
                        "property_id": "0199e3a0-0000-7000-8000-000000000401",
                        "property_display_id": "I-41",
                        "kind": "departamento",
                        "label": "Departamento 1203 · 3D2B 78 m²",
                        "unit_number": "1203",
                        "price": 3310,
                        "position": 0,
                        "released_at": null,
                        "status": "reservada"
                      },
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b22",
                        "property_id": "0199e3a0-0000-7000-8000-000000000402",
                        "property_display_id": "I-87",
                        "kind": "estacionamiento",
                        "label": "Estacionamiento E-12",
                        "unit_number": "E-12",
                        "price": 200,
                        "position": 1,
                        "released_at": null,
                        "status": "reservada"
                      }
                    ],
                    "reserva": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "cotizacion_display_id": "CZ-12",
                      "lead_id": "0199e3a0-0000-7000-8000-000000000301",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "co_buyer_contact_ids": [],
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "property_ids": [
                        "0199e3a0-0000-7000-8000-000000000401",
                        "0199e3a0-0000-7000-8000-000000000402"
                      ],
                      "status": "paid",
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "uf_value_clp": 39532.12,
                      "expires_at": "2026-10-22T15:00:00.000Z",
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_by_kind": "ui_member",
                      "created_at": "2026-10-20T15:00:00.000Z",
                      "updated_at": "2026-10-20T15:00:00.000Z",
                      "paid_at": "2026-10-21T13:10:00.000Z",
                      "expired_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/document-requirements": {
      "get": {
        "summary": "Get a proyecto’s document checklist",
        "description": "What every buyer of this proyecto is asked for, in order. A proyecto that states none gets the platform default (cédula, liquidaciones, cotizaciones AFP, pre-aprobación; plus the subsidy certificate when the proyecto takes DS19/DS1/DS49) and `is_default: true`. `reminder_days`: how often buyers are reminded of missing items. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Requirements",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioDocumentRequirements"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "key": "carnet",
                        "label": "Cédula de identidad (ambos lados)",
                        "required": true
                      },
                      {
                        "key": "liquidaciones",
                        "label": "Últimas 3 liquidaciones de sueldo",
                        "required": true
                      }
                    ],
                    "is_default": false,
                    "reminder_days": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set a proyecto’s document checklist",
        "description": "Replaces the list (an empty list returns to the default). Existing negocios gain new items on their next read; items are never removed from a negocio. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/NegocioDocumentRequirement"
                        },
                        {
                          "additionalProperties": false
                        }
                      ]
                    },
                    "maxItems": 40
                  },
                  "reminder_days": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 60
                  }
                },
                "required": [
                  "items"
                ],
                "additionalProperties": false
              },
              "example": {
                "items": [
                  {
                    "key": "carnet",
                    "label": "Cédula de identidad (ambos lados)",
                    "required": true
                  },
                  {
                    "key": "liquidaciones",
                    "label": "Últimas 3 liquidaciones de sueldo",
                    "required": true
                  }
                ],
                "reminder_days": 3
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioDocumentRequirements"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "key": "carnet",
                        "label": "Cédula de identidad (ambos lados)",
                        "required": true
                      },
                      {
                        "key": "liquidaciones",
                        "label": "Últimas 3 liquidaciones de sueldo",
                        "required": true
                      }
                    ],
                    "is_default": false,
                    "reminder_days": 3
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/documents": {
      "get": {
        "summary": "A negocio’s document checklist",
        "description": "Every item with its status (missing → received → approved | rejected), the file (name, type, size; download with `GET …/file`), where it came from (the buyer in the chat, a staff upload, a signed document) and the reminders sent. Built from the proyecto’s list on first read. Requires `leads:read`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioDocumentChecklist"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000f11",
                        "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                        "key": "liquidaciones",
                        "label": "Últimas 3 liquidaciones de sueldo",
                        "description": null,
                        "required": true,
                        "position": 1,
                        "status": "received",
                        "file": {
                          "filename": "liquidaciones.pdf",
                          "mime": "application/pdf",
                          "size": 182331,
                          "uploaded_at": "2026-10-22T14:03:00.000Z"
                        },
                        "source": "chat",
                        "message_id": "88213",
                        "received_at": "2026-10-22T14:03:00.000Z",
                        "reviewed_at": null,
                        "reviewed_by": null,
                        "review_note": null,
                        "reminder_count": 1,
                        "last_reminded_at": "2026-10-21T13:00:00.000Z",
                        "created_at": "2026-10-21T13:10:00.000Z",
                        "updated_at": "2026-10-22T14:03:00.000Z"
                      }
                    ],
                    "summary": {
                      "required": 4,
                      "received": 1,
                      "approved": 0,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/documents/{documentId}/file": {
      "get": {
        "summary": "Download a checklist document",
        "description": "The stored bytes, streamed through the API (`nosniff`, `Cache-Control: private, no-store`, sandbox CSP); every read is audited. Requires `leads:read`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "documentId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file bytes",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Upload a document onto a checklist item",
        "description": "`multipart/form-data` with one `file` part (or the JSON file shape), at most 25 MB; images, PDF and office documents. The item becomes `received`; uploading again replaces it and clears the review. Requires `leads:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "documentId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "liquidaciones.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Filed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioDocument"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000f11",
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "key": "liquidaciones",
                    "label": "Últimas 3 liquidaciones de sueldo",
                    "description": null,
                    "required": true,
                    "position": 1,
                    "status": "received",
                    "file": {
                      "filename": "liquidaciones.pdf",
                      "mime": "application/pdf",
                      "size": 182331,
                      "uploaded_at": "2026-10-22T14:03:00.000Z"
                    },
                    "source": "chat",
                    "message_id": "88213",
                    "received_at": "2026-10-22T14:03:00.000Z",
                    "reviewed_at": null,
                    "reviewed_by": null,
                    "review_note": null,
                    "reminder_count": 1,
                    "last_reminded_at": "2026-10-21T13:00:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-22T14:03:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/documents/{documentId}/review": {
      "post": {
        "summary": "Approve or reject a checklist document",
        "description": "A received item only (409 `document_missing`). Rejecting needs a `note`: the buyer gets it by WhatsApp (inside the 24 h window) with a request to send it again, and reminders resume. Requires `leads:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "documentId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "approved",
                      "rejected"
                    ]
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500,
                    "description": "Required to reject: the buyer reads it."
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false
              },
              "example": {
                "status": "rejected",
                "note": "La liquidación de agosto no se lee"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Reviewed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioDocument"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000f11",
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "key": "liquidaciones",
                    "label": "Últimas 3 liquidaciones de sueldo",
                    "description": null,
                    "required": true,
                    "position": 1,
                    "status": "rejected",
                    "file": {
                      "filename": "liquidaciones.pdf",
                      "mime": "application/pdf",
                      "size": 182331,
                      "uploaded_at": "2026-10-22T14:03:00.000Z"
                    },
                    "source": "chat",
                    "message_id": "88213",
                    "received_at": "2026-10-22T14:03:00.000Z",
                    "reviewed_at": null,
                    "reviewed_by": null,
                    "review_note": "La liquidación de agosto no se lee",
                    "reminder_count": 1,
                    "last_reminded_at": "2026-10-21T13:00:00.000Z",
                    "created_at": "2026-10-21T13:10:00.000Z",
                    "updated_at": "2026-10-22T14:03:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/bank-approvals": {
      "get": {
        "summary": "A negocio’s bank approvals",
        "description": "One row per bank: en_evaluacion → pre_aprobado → aprobado | rechazado, with amount, rate, term and dates, side by side. Requires `leads:read`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Bank approvals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NegocioBankApproval"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000f21",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "bank": "Banco Estado",
                      "status": "pre_aprobado",
                      "amount": 2800,
                      "currency": "UF",
                      "rate_annual": 4.39,
                      "term_years": 30,
                      "decided_on": "2026-10-28",
                      "expires_on": "2027-01-28",
                      "notes": null,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-25T12:00:00.000Z",
                      "updated_at": "2026-10-28T12:00:00.000Z"
                    },
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000f22",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "bank": "Banco de Chile",
                      "status": "en_evaluacion",
                      "amount": null,
                      "currency": null,
                      "rate_annual": null,
                      "term_years": null,
                      "decided_on": null,
                      "expires_on": null,
                      "notes": null,
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-25T12:00:00.000Z",
                      "updated_at": "2026-10-28T12:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Track a bank’s approval",
        "description": "One row per bank and negocio (409 `bank_already_tracked`). `amount` and `currency` go together. Requires `leads:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bank": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "The bank, e.g. «Banco Estado»."
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "en_evaluacion",
                      "pre_aprobado",
                      "aprobado",
                      "rechazado"
                    ],
                    "description": "Where this bank’s crédito hipotecario stands."
                  },
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "exclusiveMinimum": 0,
                    "maximum": 1000000000
                  },
                  "currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "UF",
                      "CLP"
                    ]
                  },
                  "rate_annual": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30
                  },
                  "term_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 40
                  },
                  "decided_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "expires_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "bank"
                ],
                "additionalProperties": false
              },
              "example": {
                "bank": "Banco Estado",
                "status": "en_evaluacion"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Tracked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioBankApproval"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000f21",
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "bank": "Banco Estado",
                    "status": "pre_aprobado",
                    "amount": 2800,
                    "currency": "UF",
                    "rate_annual": 4.39,
                    "term_years": 30,
                    "decided_on": "2026-10-28",
                    "expires_on": "2027-01-28",
                    "notes": null,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-25T12:00:00.000Z",
                    "updated_at": "2026-10-28T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/bank-approvals/{approvalId}": {
      "delete": {
        "summary": "Stop tracking a bank",
        "description": "Requires `leads:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "approvalId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "deleted": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        }
                      },
                      "required": [
                        "id",
                        "deleted"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000f21",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a bank’s approval",
        "description": "Any field; the status moves freely as the bank answers. Requires `leads:write`. Only on a workspace that runs the Proyectos module; any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "approvalId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bank": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "en_evaluacion",
                      "pre_aprobado",
                      "aprobado",
                      "rechazado"
                    ],
                    "description": "Where this bank’s crédito hipotecario stands."
                  },
                  "amount": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "exclusiveMinimum": 0,
                    "maximum": 1000000000
                  },
                  "currency": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "UF",
                      "CLP"
                    ]
                  },
                  "rate_annual": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 30
                  },
                  "term_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 40
                  },
                  "decided_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "expires_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "YYYY-MM-DD"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "status": "aprobado",
                "amount": 2800,
                "currency": "UF",
                "rate_annual": 4.29,
                "term_years": 30,
                "decided_on": "2026-11-05"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioBankApproval"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000f21",
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "bank": "Banco Estado",
                    "status": "aprobado",
                    "amount": 2800,
                    "currency": "UF",
                    "rate_annual": 4.39,
                    "term_years": 30,
                    "decided_on": "2026-10-28",
                    "expires_on": "2027-01-28",
                    "notes": null,
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-25T12:00:00.000Z",
                    "updated_at": "2026-10-28T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/signing-templates/{kind}": {
      "get": {
        "summary": "Get a proyecto’s signing template",
        "description": "The text of a signable document (the orden de reserva) with its {{placeholders}}; `is_default` when the proyecto uses the platform text. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "orden_reserva"
              ],
              "description": "The document: the orden de reserva."
            },
            "required": true,
            "description": "The document: the orden de reserva.",
            "name": "kind",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioSigningTemplate"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "orden_reserva",
                    "title": "Orden de reserva",
                    "body": "# Orden de reserva\n\nEn {{ciudad}}, el {{fecha}}, {{comprador_nombre}} reserva…",
                    "is_default": true,
                    "placeholders": [
                      "inmobiliaria",
                      "proyecto",
                      "comprador_nombre",
                      "unidades",
                      "precio_total"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set a proyecto’s signing template",
        "description": "Title and body (headings with `#`, paragraphs, {{placeholders}}); it must name the buyer ({{comprador_nombre}}). Applies to documents generated afterwards; a request already sent keeps its PDF. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "orden_reserva"
              ],
              "description": "The document: the orden de reserva."
            },
            "required": true,
            "description": "The document: the orden de reserva.",
            "name": "kind",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "body": {
                    "type": "string",
                    "minLength": 20,
                    "maxLength": 20000
                  }
                },
                "required": [
                  "title",
                  "body"
                ],
                "additionalProperties": false
              },
              "example": {
                "title": "Orden de reserva",
                "body": "# Orden de reserva\n\nEn {{ciudad}}, el {{fecha}}, {{comprador_nombre}} reserva…"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioSigningTemplate"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "orden_reserva",
                    "title": "Orden de reserva",
                    "body": "# Orden de reserva\n\nEn {{ciudad}}, el {{fecha}}, {{comprador_nombre}} reserva…",
                    "is_default": false,
                    "placeholders": [
                      "inmobiliaria",
                      "proyecto",
                      "comprador_nombre",
                      "unidades",
                      "precio_total"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Reset a proyecto’s signing template",
        "description": "Back to the platform text. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "orden_reserva"
              ],
              "description": "The document: the orden de reserva."
            },
            "required": true,
            "description": "The document: the orden de reserva.",
            "name": "kind",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Default template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioSigningTemplate"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kind": "orden_reserva",
                    "title": "Orden de reserva",
                    "body": "# Orden de reserva\n\nEn {{ciudad}}, el {{fecha}}, {{comprador_nombre}} reserva…",
                    "is_default": true,
                    "placeholders": [
                      "inmobiliaria",
                      "proyecto",
                      "comprador_nombre",
                      "unidades",
                      "precio_total"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/negocios/{id}/signatures": {
      "get": {
        "summary": "A negocio’s signing requests",
        "description": "Every request, newest first, with its status, the document hash, which files exist and the evidence trail (requested, code sent/verified, cédula and selfie submitted with their hashes, sent to the provider, signed — each with instant, IP and user agent). Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Signatures",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NegocioSignature"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000c11",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "kind": "orden_reserva",
                      "provider": "documenso",
                      "status": "pending_identity",
                      "title": "Orden de reserva",
                      "signer_name": "Camila Rojas",
                      "signer_phone_hint": "+56•••••4567",
                      "document_sha256": "9f2c…",
                      "otp_channel": null,
                      "otp_verified_at": null,
                      "identity_submitted_at": null,
                      "signed_at": null,
                      "expires_at": "2026-10-29T15:00:00.000Z",
                      "cancelled_at": null,
                      "files": {
                        "unsigned": true,
                        "signed": false,
                        "certificate": false,
                        "id_front": false,
                        "selfie": false
                      },
                      "evidence": [
                        {
                          "at": "2026-10-22T15:00:00.000Z",
                          "event": "requested"
                        }
                      ],
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-22T15:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Send a negocio document to sign",
        "description": "Generates the document (the proyecto’s template filled with the negocio, its cotización and reserva) as a branded PDF, freezes its SHA-256 and sends the buyer a WhatsApp link (`deliver`, default true, as the teammate; `link` is returned once to copy). On the link the buyer reads the PDF, types an SMS code, uploads a photo of the cédula and a selfie, and signs with the provider (Documenso, FES). The signed PDF and the certificate land on the negocio checklist and the ejecutivo is told. 409 `signature_pending`, `negocio_stage_conflict`, `signing_not_configured`. Requires `leads:write` (+ `messages:send`, `conversations:write` to deliver). Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "kind": {
                    "type": "string",
                    "enum": [
                      "orden_reserva"
                    ],
                    "description": "The document: the orden de reserva."
                  },
                  "deliver": {
                    "type": "boolean",
                    "description": "Send the link on the buyer’s WhatsApp now (default true)."
                  }
                },
                "required": [
                  "kind"
                ],
                "additionalProperties": false
              },
              "example": {
                "kind": "orden_reserva"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Requested",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "signature": {
                          "$ref": "#/components/schemas/NegocioSignature"
                        },
                        "link": {
                          "type": "string",
                          "description": "The buyer’s signing link (shown once)."
                        },
                        "text": {
                          "type": "string"
                        },
                        "delivery": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "sent",
                                "retrying",
                                "pending_approval",
                                "not_sent"
                              ]
                            },
                            "reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "conversation_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "channel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "status",
                            "reason",
                            "message_id",
                            "conversation_id",
                            "channel"
                          ]
                        }
                      },
                      "required": [
                        "signature",
                        "link",
                        "text",
                        "delivery"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "signature": {
                      "id": "0199e3a0-0000-7000-8000-000000000c11",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "kind": "orden_reserva",
                      "provider": "documenso",
                      "status": "pending_identity",
                      "title": "Orden de reserva",
                      "signer_name": "Camila Rojas",
                      "signer_phone_hint": "+56•••••4567",
                      "document_sha256": "9f2c…",
                      "otp_channel": null,
                      "otp_verified_at": null,
                      "identity_submitted_at": null,
                      "signed_at": null,
                      "expires_at": "2026-10-29T15:00:00.000Z",
                      "cancelled_at": null,
                      "files": {
                        "unsigned": true,
                        "signed": false,
                        "certificate": false,
                        "id_front": false,
                        "selfie": false
                      },
                      "evidence": [
                        {
                          "at": "2026-10-22T15:00:00.000Z",
                          "event": "requested"
                        }
                      ],
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-22T15:00:00.000Z"
                    },
                    "link": "https://app.vitrinadev.com/firmar/q9Xc…",
                    "text": "Hola Camila, te enviamos tu orden de reserva para firmar: …",
                    "delivery": {
                      "status": "sent",
                      "reason": null,
                      "message_id": "88300",
                      "conversation_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "channel": "whatsapp"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/signatures/{signatureId}/resend": {
      "post": {
        "summary": "Send a new signing link",
        "description": "A live request only (409 `signature_not_live`); the previous link stops working. Requires `leads:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "signatureId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "deliver": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Re-sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "signature": {
                          "$ref": "#/components/schemas/NegocioSignature"
                        },
                        "link": {
                          "type": "string",
                          "description": "The buyer’s signing link (shown once)."
                        },
                        "text": {
                          "type": "string"
                        },
                        "delivery": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "sent",
                                "retrying",
                                "pending_approval",
                                "not_sent"
                              ]
                            },
                            "reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "message_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "conversation_id": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "channel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "status",
                            "reason",
                            "message_id",
                            "conversation_id",
                            "channel"
                          ]
                        }
                      },
                      "required": [
                        "signature",
                        "link",
                        "text",
                        "delivery"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "signature": {
                      "id": "0199e3a0-0000-7000-8000-000000000c11",
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "kind": "orden_reserva",
                      "provider": "documenso",
                      "status": "pending_identity",
                      "title": "Orden de reserva",
                      "signer_name": "Camila Rojas",
                      "signer_phone_hint": "+56•••••4567",
                      "document_sha256": "9f2c…",
                      "otp_channel": null,
                      "otp_verified_at": null,
                      "identity_submitted_at": null,
                      "signed_at": null,
                      "expires_at": "2026-10-29T15:00:00.000Z",
                      "cancelled_at": null,
                      "files": {
                        "unsigned": true,
                        "signed": false,
                        "certificate": false,
                        "id_front": false,
                        "selfie": false
                      },
                      "evidence": [
                        {
                          "at": "2026-10-22T15:00:00.000Z",
                          "event": "requested"
                        }
                      ],
                      "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-22T15:00:00.000Z"
                    },
                    "link": "https://app.vitrinadev.com/firmar/Zr3…",
                    "text": "…",
                    "delivery": {
                      "status": "sent",
                      "reason": null,
                      "message_id": "88301",
                      "conversation_id": null,
                      "channel": "whatsapp"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/signatures/{signatureId}/cancel": {
      "post": {
        "summary": "Cancel a signing request",
        "description": "The link stops working and the provider envelope is cancelled. Requires `leads:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "signatureId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Cancelled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioSignature"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000c11",
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "kind": "orden_reserva",
                    "provider": "documenso",
                    "status": "cancelled",
                    "title": "Orden de reserva",
                    "signer_name": "Camila Rojas",
                    "signer_phone_hint": "+56•••••4567",
                    "document_sha256": "9f2c…",
                    "otp_channel": null,
                    "otp_verified_at": null,
                    "identity_submitted_at": null,
                    "signed_at": null,
                    "expires_at": "2026-10-29T15:00:00.000Z",
                    "cancelled_at": null,
                    "files": {
                      "unsigned": true,
                      "signed": false,
                      "certificate": false,
                      "id_front": false,
                      "selfie": false
                    },
                    "evidence": [
                      {
                        "at": "2026-10-22T15:00:00.000Z",
                        "event": "requested"
                      }
                    ],
                    "created_by": "0199e3a0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-22T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/negocios/{id}/signatures/{signatureId}/files/{file}": {
      "get": {
        "summary": "Download a signing file",
        "description": "`unsigned` (what was sent), `signed`, `certificate` (the provider’s), `id_front` or `selfie`. Streamed, `no-store`, audited. Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "signatureId",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "unsigned",
                "signed",
                "certificate",
                "id_front",
                "selfie"
              ]
            },
            "required": true,
            "name": "file",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The file",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/pie-terms": {
      "get": {
        "summary": "Get a proyecto’s cuota reminders",
        "description": "When buyers are reminded of a pie cuota. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Terms",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioPieTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reminder_days_before": 3,
                    "reminder_days_after": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set a proyecto’s cuota reminders",
        "description": "Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/NegocioPieTerms"
                  },
                  {
                    "additionalProperties": false
                  }
                ]
              },
              "example": {
                "reminder_days_before": 3,
                "reminder_days_after": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioPieTerms"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "reminder_days_before": 3,
                    "reminder_days_after": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/pie-plan": {
      "post": {
        "summary": "Split the pie into cuotas",
        "description": "The pie (default: the accepted cotización’s pie minus the reserva already paid, in the negocio’s currency) in 1–36 monthly cuotas from `first_due_on` (default a month from today); the last cuota absorbs the rounding. Each cuota is a `real_estate_pie` obligation of the buyer, paid by its own link (`GET …/cuotas/{number}/payment`) or by transfer (the ledger’s matcher settles the oldest open cuota first). A UF cuota is charged at the day’s UF until money is applied to it. The buyer is reminded before and after each due day (`/projects/{id}/pie-terms`). 409 `pie_plan_exists` (pass `replace` while nothing is paid), `pie_plan_has_payments`, `negocio_stage_conflict`, `uf_unavailable`. Requires `leads:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "installments": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 36,
                    "description": "Default: the cotización’s `pie_installments`."
                  },
                  "total": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 1000000000,
                    "description": "The pie to split, in the negocio’s currency. Default: the cotización’s pie minus the reserva already paid."
                  },
                  "first_due_on": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                    "description": "Default: a month from today."
                  },
                  "replace": {
                    "type": "boolean",
                    "description": "Replace an existing plan that has no payments yet."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "installments": 12,
                "first_due_on": "2026-11-20"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The statement with the new plan",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioStatement"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "buyer_name": "Camila Rojas",
                    "project_name": "Parque Los Aromos",
                    "currency": "UF",
                    "total": 3510,
                    "reserva": {
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "paid_at": "2026-10-21T13:10:00.000Z"
                    },
                    "cuotas": [
                      {
                        "number": 1,
                        "due_on": "2026-11-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "pending",
                        "is_overdue": false
                      },
                      {
                        "number": 2,
                        "due_on": "2026-12-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c22",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "pending",
                        "is_overdue": false
                      }
                    ],
                    "totals": {
                      "pie_clp": 27751548,
                      "paid_clp": 0,
                      "outstanding_clp": 27751548,
                      "overdue_clp": 0,
                      "cuotas_paid": 0,
                      "cuotas_overdue": 0
                    },
                    "next_due": {
                      "number": 1,
                      "due_on": "2026-11-20",
                      "amount": 58.5,
                      "currency": "UF",
                      "amount_clp": 2312629,
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                      "paid_clp": 0,
                      "outstanding_clp": 2312629,
                      "state": "pending",
                      "is_overdue": false
                    },
                    "as_of": "2026-10-22"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/statement": {
      "get": {
        "summary": "A negocio’s account statement",
        "description": "The estado de cuenta: the reserva and every cuota with what is paid, owed and overdue (from the ledger), the totals and the next cuota due. Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Statement",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioStatement"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "display_id": "NG-3",
                    "buyer_name": "Camila Rojas",
                    "project_name": "Parque Los Aromos",
                    "currency": "UF",
                    "total": 3510,
                    "reserva": {
                      "amount": 20,
                      "currency": "UF",
                      "amount_clp": 790642,
                      "paid_at": "2026-10-21T13:10:00.000Z"
                    },
                    "cuotas": [
                      {
                        "number": 1,
                        "due_on": "2026-11-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "pending",
                        "is_overdue": false
                      },
                      {
                        "number": 2,
                        "due_on": "2026-12-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c22",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "pending",
                        "is_overdue": false
                      }
                    ],
                    "totals": {
                      "pie_clp": 27751548,
                      "paid_clp": 0,
                      "outstanding_clp": 27751548,
                      "overdue_clp": 0,
                      "cuotas_paid": 0,
                      "cuotas_overdue": 0
                    },
                    "next_due": {
                      "number": 1,
                      "due_on": "2026-11-20",
                      "amount": 58.5,
                      "currency": "UF",
                      "amount_clp": 2312629,
                      "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                      "paid_clp": 0,
                      "outstanding_clp": 2312629,
                      "state": "pending",
                      "is_overdue": false
                    },
                    "as_of": "2026-10-22"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/statement/pdf": {
      "get": {
        "summary": "The account statement as a PDF",
        "description": "Branded with the workspace’s paper. Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/statement/send": {
      "post": {
        "summary": "Send the account statement to the buyer",
        "description": "The PDF on the buyer’s WhatsApp thread, inside Meta’s 24 h window (409 `statement_window_closed`, `statement_no_thread`). The buyer can also ask the agent for it. Requires `leads:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "boolean",
                          "enum": [
                            true
                          ]
                        },
                        "statement": {
                          "$ref": "#/components/schemas/NegocioStatement"
                        }
                      },
                      "required": [
                        "sent",
                        "statement"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "sent": true,
                    "statement": {
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "display_id": "NG-3",
                      "buyer_name": "Camila Rojas",
                      "project_name": "Parque Los Aromos",
                      "currency": "UF",
                      "total": 3510,
                      "reserva": {
                        "amount": 20,
                        "currency": "UF",
                        "amount_clp": 790642,
                        "paid_at": "2026-10-21T13:10:00.000Z"
                      },
                      "cuotas": [
                        {
                          "number": 1,
                          "due_on": "2026-11-20",
                          "amount": 58.5,
                          "currency": "UF",
                          "amount_clp": 2312629,
                          "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                          "paid_clp": 0,
                          "outstanding_clp": 2312629,
                          "state": "pending",
                          "is_overdue": false
                        },
                        {
                          "number": 2,
                          "due_on": "2026-12-20",
                          "amount": 58.5,
                          "currency": "UF",
                          "amount_clp": 2312629,
                          "obligation_id": "0199e3a0-0000-7000-8000-000000000c22",
                          "paid_clp": 0,
                          "outstanding_clp": 2312629,
                          "state": "pending",
                          "is_overdue": false
                        }
                      ],
                      "totals": {
                        "pie_clp": 27751548,
                        "paid_clp": 0,
                        "outstanding_clp": 27751548,
                        "overdue_clp": 0,
                        "cuotas_paid": 0,
                        "cuotas_overdue": 0
                      },
                      "next_due": {
                        "number": 1,
                        "due_on": "2026-11-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "pending",
                        "is_overdue": false
                      },
                      "as_of": "2026-10-22"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/negocios/{id}/cuotas/{number}/payment": {
      "get": {
        "summary": "How a cuota can be paid",
        "description": "The cuota’s payment link (the connected provider) and/or transfer accounts, by the workspace’s charging preference. 409 `cuota_not_open`. Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 36
            },
            "required": true,
            "name": "number",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Payment options",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NegocioCuotaPayment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "number": 1,
                    "due_on": "2026-11-20",
                    "amount_clp": 2312629,
                    "mode": "mp_first",
                    "link": {
                      "checkout_id": "0199e3a0-0000-7000-8000-000000000d02",
                      "checkout_url": "https://api.vitrinadev.com/p/…",
                      "amount_clp": 2312629
                    },
                    "transfer_accounts": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cobranza": {
      "get": {
        "summary": "Overdue pie cuotas",
        "description": "Every cuota past its due day with money still owed, oldest first, with the negocio, buyer, proyecto, ejecutivo and days overdue. Filter by `project_id`. Requires `payments:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos’ negocios (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              },
              "maxItems": 50
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Cobranza",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NegocioCobranzaRow"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                      "display_id": "NG-3",
                      "buyer_name": "Camila Rojas",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000302",
                      "days_overdue": 4,
                      "cuota": {
                        "number": 1,
                        "due_on": "2026-11-20",
                        "amount": 58.5,
                        "currency": "UF",
                        "amount_clp": 2312629,
                        "obligation_id": "0199e3a0-0000-7000-8000-000000000c21",
                        "paid_clp": 0,
                        "outstanding_clp": 2312629,
                        "state": "overdue",
                        "is_overdue": true
                      }
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "limit": 100,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones/{id}/discount-approval": {
      "get": {
        "summary": "Where a cotización’s discount stands against the caller’s limit",
        "description": "The caller’s role limit (`limit_percent`, null = none), the cotización’s effective `discount_percent`, whether it `exceeds_limit`, the highest discount an approval has allowed (`approved_percent`), whether the caller `can_send` it as it stands, the open request (`pending`) and every request (`approvals`, newest first, with who decided and why). A refusal returns the cotización to the ejecutivo with its `decision_reason`. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The standing",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "limit_percent": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The caller’s role limit (`max_discount_percent`), percent; null = no limit."
                        },
                        "discount_percent": {
                          "type": "number",
                          "description": "The cotización’s effective discount."
                        },
                        "exceeds_limit": {
                          "type": "boolean",
                          "description": "The discount is above the caller’s limit."
                        },
                        "approved_percent": {
                          "type": [
                            "number",
                            "null"
                          ],
                          "description": "The highest discount an approval has allowed, if any."
                        },
                        "can_send": {
                          "type": "boolean",
                          "description": "The caller may send it as it stands (within limit, or approved)."
                        },
                        "pending": {
                          "type": [
                            "object",
                            "null"
                          ],
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "cotizacion_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "approved",
                                "refused",
                                "superseded"
                              ],
                              "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                            },
                            "requested_by": {
                              "type": "string",
                              "format": "uuid",
                              "description": "The ejecutivo (auth user id) who asked."
                            },
                            "requested_at": {
                              "type": "string"
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Why the ejecutivo asked."
                            },
                            "requested_percent": {
                              "type": "number",
                              "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                            },
                            "limit_percent": {
                              "type": "number",
                              "description": "The requester’s role limit when they asked."
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "UF",
                                "CLP"
                              ],
                              "description": "The currency the offer is written in."
                            },
                            "list_total": {
                              "type": "number"
                            },
                            "discount_total": {
                              "type": "number"
                            },
                            "total": {
                              "type": "number"
                            },
                            "decided_by": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "decided_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "decision_reason": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Always present on a refusal."
                            }
                          },
                          "required": [
                            "id",
                            "cotizacion_id",
                            "status",
                            "requested_by",
                            "requested_at",
                            "note",
                            "requested_percent",
                            "limit_percent",
                            "currency",
                            "list_total",
                            "discount_total",
                            "total",
                            "decided_by",
                            "decided_at",
                            "decision_reason"
                          ]
                        },
                        "approvals": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "cotizacion_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "pending",
                                  "approved",
                                  "refused",
                                  "superseded"
                                ],
                                "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                              },
                              "requested_by": {
                                "type": "string",
                                "format": "uuid",
                                "description": "The ejecutivo (auth user id) who asked."
                              },
                              "requested_at": {
                                "type": "string"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Why the ejecutivo asked."
                              },
                              "requested_percent": {
                                "type": "number",
                                "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                              },
                              "limit_percent": {
                                "type": "number",
                                "description": "The requester’s role limit when they asked."
                              },
                              "currency": {
                                "type": "string",
                                "enum": [
                                  "UF",
                                  "CLP"
                                ],
                                "description": "The currency the offer is written in."
                              },
                              "list_total": {
                                "type": "number"
                              },
                              "discount_total": {
                                "type": "number"
                              },
                              "total": {
                                "type": "number"
                              },
                              "decided_by": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "format": "uuid"
                              },
                              "decided_at": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "decision_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "description": "Always present on a refusal."
                              }
                            },
                            "required": [
                              "id",
                              "cotizacion_id",
                              "status",
                              "requested_by",
                              "requested_at",
                              "note",
                              "requested_percent",
                              "limit_percent",
                              "currency",
                              "list_total",
                              "discount_total",
                              "total",
                              "decided_by",
                              "decided_at",
                              "decision_reason"
                            ]
                          },
                          "description": "Newest first."
                        }
                      },
                      "required": [
                        "limit_percent",
                        "discount_percent",
                        "exceeds_limit",
                        "approved_percent",
                        "can_send",
                        "pending",
                        "approvals"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "limit_percent": 3,
                    "discount_percent": 6,
                    "exceeds_limit": true,
                    "approved_percent": null,
                    "can_send": false,
                    "pending": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "status": "pending",
                      "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "requested_at": "2026-10-20T12:00:00.000Z",
                      "note": "Cliente cierra hoy si llegamos al 6 %.",
                      "requested_percent": 6,
                      "limit_percent": 3,
                      "currency": "UF",
                      "list_total": 3510,
                      "discount_total": 210.6,
                      "total": 3299.4,
                      "decided_by": null,
                      "decided_at": null,
                      "decision_reason": null
                    },
                    "approvals": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000a01",
                        "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                        "status": "pending",
                        "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                        "requested_at": "2026-10-20T12:00:00.000Z",
                        "note": "Cliente cierra hoy si llegamos al 6 %.",
                        "requested_percent": 6,
                        "limit_percent": 3,
                        "currency": "UF",
                        "list_total": 3510,
                        "discount_total": 210.6,
                        "total": 3299.4,
                        "decided_by": null,
                        "decided_at": null,
                        "decision_reason": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Ask a jefe de sala or gerente to allow the discount",
        "description": "A draft whose discount is above the caller’s role limit cannot be sent (409 `discount_approval_required` on `POST /cotizaciones/{id}/send`) until a request is approved. This opens the request and tells, in the bell, every member holding `cotizacion_discount:approve` whose proyectos include this one. One request is open per cotización: asking again returns it (200, `created: false`) without telling anyone twice. Editing the draft’s discount supersedes the open request. 409 `discount_within_limit` when no approval is needed; 409 `cotizacion_not_draft` once sent. Requires `leads:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Why the buyer needs this discount (the approver reads it)."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "note": "Cliente cierra hoy si llegamos al 6 %."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already open",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "approval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "cotizacion_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "approved",
                                "refused",
                                "superseded"
                              ],
                              "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                            },
                            "requested_by": {
                              "type": "string",
                              "format": "uuid",
                              "description": "The ejecutivo (auth user id) who asked."
                            },
                            "requested_at": {
                              "type": "string"
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Why the ejecutivo asked."
                            },
                            "requested_percent": {
                              "type": "number",
                              "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                            },
                            "limit_percent": {
                              "type": "number",
                              "description": "The requester’s role limit when they asked."
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "UF",
                                "CLP"
                              ],
                              "description": "The currency the offer is written in."
                            },
                            "list_total": {
                              "type": "number"
                            },
                            "discount_total": {
                              "type": "number"
                            },
                            "total": {
                              "type": "number"
                            },
                            "decided_by": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "decided_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "decision_reason": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Always present on a refusal."
                            }
                          },
                          "required": [
                            "id",
                            "cotizacion_id",
                            "status",
                            "requested_by",
                            "requested_at",
                            "note",
                            "requested_percent",
                            "limit_percent",
                            "currency",
                            "list_total",
                            "discount_total",
                            "total",
                            "decided_by",
                            "decided_at",
                            "decision_reason"
                          ]
                        },
                        "created": {
                          "type": "boolean",
                          "description": "False when a request was already open (returned as it is)."
                        },
                        "notified": {
                          "type": "integer",
                          "description": "Approvers told in the bell."
                        }
                      },
                      "required": [
                        "approval",
                        "created",
                        "notified"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "approval": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "status": "pending",
                      "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "requested_at": "2026-10-20T12:00:00.000Z",
                      "note": "Cliente cierra hoy si llegamos al 6 %.",
                      "requested_percent": 6,
                      "limit_percent": 3,
                      "currency": "UF",
                      "list_total": 3510,
                      "discount_total": 210.6,
                      "total": 3299.4,
                      "decided_by": null,
                      "decided_at": null,
                      "decision_reason": null
                    },
                    "created": false,
                    "notified": 0
                  }
                }
              }
            }
          },
          "201": {
            "description": "Opened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "approval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "cotizacion_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "approved",
                                "refused",
                                "superseded"
                              ],
                              "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                            },
                            "requested_by": {
                              "type": "string",
                              "format": "uuid",
                              "description": "The ejecutivo (auth user id) who asked."
                            },
                            "requested_at": {
                              "type": "string"
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Why the ejecutivo asked."
                            },
                            "requested_percent": {
                              "type": "number",
                              "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                            },
                            "limit_percent": {
                              "type": "number",
                              "description": "The requester’s role limit when they asked."
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "UF",
                                "CLP"
                              ],
                              "description": "The currency the offer is written in."
                            },
                            "list_total": {
                              "type": "number"
                            },
                            "discount_total": {
                              "type": "number"
                            },
                            "total": {
                              "type": "number"
                            },
                            "decided_by": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "decided_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "decision_reason": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Always present on a refusal."
                            }
                          },
                          "required": [
                            "id",
                            "cotizacion_id",
                            "status",
                            "requested_by",
                            "requested_at",
                            "note",
                            "requested_percent",
                            "limit_percent",
                            "currency",
                            "list_total",
                            "discount_total",
                            "total",
                            "decided_by",
                            "decided_at",
                            "decision_reason"
                          ]
                        },
                        "created": {
                          "type": "boolean",
                          "description": "False when a request was already open (returned as it is)."
                        },
                        "notified": {
                          "type": "integer",
                          "description": "Approvers told in the bell."
                        }
                      },
                      "required": [
                        "approval",
                        "created",
                        "notified"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "approval": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "status": "pending",
                      "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "requested_at": "2026-10-20T12:00:00.000Z",
                      "note": "Cliente cierra hoy si llegamos al 6 %.",
                      "requested_percent": 6,
                      "limit_percent": 3,
                      "currency": "UF",
                      "list_total": 3510,
                      "discount_total": 210.6,
                      "total": 3299.4,
                      "decided_by": null,
                      "decided_at": null,
                      "decision_reason": null
                    },
                    "created": true,
                    "notified": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizacion-discount-approvals": {
      "get": {
        "summary": "The approver’s queue of discount requests",
        "description": "Requests with the cotización, buyer and proyecto they are about, newest first. Default `status=pending`. Narrowed to the caller’s proyectos (a jefe de sala sees their sala’s; a gerente all). Requires `cotizacion_discount:approve`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "pending",
                  "approved",
                  "refused",
                  "superseded"
                ],
                "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
              },
              "maxItems": 50,
              "description": "Comma-separated statuses. Default: pending."
            },
            "required": false,
            "description": "Comma-separated statuses. Default: pending.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "Default 50."
            },
            "required": false,
            "description": "Default 50.",
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The queue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "cotizacion_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "pending",
                              "approved",
                              "refused",
                              "superseded"
                            ],
                            "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                          },
                          "requested_by": {
                            "type": "string",
                            "format": "uuid",
                            "description": "The ejecutivo (auth user id) who asked."
                          },
                          "requested_at": {
                            "type": "string"
                          },
                          "note": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Why the ejecutivo asked."
                          },
                          "requested_percent": {
                            "type": "number",
                            "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                          },
                          "limit_percent": {
                            "type": "number",
                            "description": "The requester’s role limit when they asked."
                          },
                          "currency": {
                            "type": "string",
                            "enum": [
                              "UF",
                              "CLP"
                            ],
                            "description": "The currency the offer is written in."
                          },
                          "list_total": {
                            "type": "number"
                          },
                          "discount_total": {
                            "type": "number"
                          },
                          "total": {
                            "type": "number"
                          },
                          "decided_by": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "decided_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "decision_reason": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "description": "Always present on a refusal."
                          },
                          "cotizacion_display_id": {
                            "type": "string"
                          },
                          "cotizacion_status": {
                            "type": "string",
                            "enum": [
                              "draft",
                              "sent",
                              "accepted",
                              "rejected",
                              "voided"
                            ],
                            "description": "draft: editable. sent: frozen (figures, lines, UF, list version, financing). accepted | rejected | voided are moved by later slices (reserva, discounts)."
                          },
                          "buyer_name": {
                            "type": "string"
                          },
                          "project_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "project_name": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "owner_user_id": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          }
                        },
                        "required": [
                          "id",
                          "cotizacion_id",
                          "status",
                          "requested_by",
                          "requested_at",
                          "note",
                          "requested_percent",
                          "limit_percent",
                          "currency",
                          "list_total",
                          "discount_total",
                          "total",
                          "decided_by",
                          "decided_at",
                          "decision_reason",
                          "cotizacion_display_id",
                          "cotizacion_status",
                          "buyer_name",
                          "project_id",
                          "project_name",
                          "owner_user_id"
                        ]
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {}
                      }
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "status": "pending",
                      "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "requested_at": "2026-10-20T12:00:00.000Z",
                      "note": "Cliente cierra hoy si llegamos al 6 %.",
                      "requested_percent": 6,
                      "limit_percent": 3,
                      "currency": "UF",
                      "list_total": 3510,
                      "discount_total": 210.6,
                      "total": 3299.4,
                      "decided_by": null,
                      "decided_at": null,
                      "decision_reason": null,
                      "cotizacion_display_id": "CZ-12",
                      "cotizacion_status": "draft",
                      "buyer_name": "Camila Rojas",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Parque Los Aromos",
                      "owner_user_id": "0199e3a0-0000-7000-8000-0000000000bb"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizacion-discount-approvals/{id}/decision": {
      "post": {
        "summary": "Approve or refuse a discount request",
        "description": "`approve` makes the cotización sendable for any discount up to the one asked; `refuse` needs a `reason`, which returns to the ejecutivo with the cotización. Terminal: a decided request cannot be decided again (409 `approval_not_pending`). Nobody decides their own request (409 `self_decision`). The ejecutivo is told in the bell and both outcomes are audited. 404 when the cotización is outside the caller’s proyectos. Requires `cotizacion_discount:approve` alone. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The discount approval’s id."
            },
            "required": true,
            "description": "The discount approval’s id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "decision": {
                    "type": "string",
                    "enum": [
                      "approve",
                      "refuse"
                    ]
                  },
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1000,
                    "description": "Required to refuse; the ejecutivo reads it."
                  }
                },
                "required": [
                  "decision"
                ],
                "additionalProperties": false
              },
              "example": {
                "decision": "approve"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decided",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "approval": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "cotizacion_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "pending",
                                "approved",
                                "refused",
                                "superseded"
                              ],
                              "description": "pending: waiting for a jefe de sala or gerente. approved | refused: decided (terminal). superseded: the draft’s discount changed while it waited."
                            },
                            "requested_by": {
                              "type": "string",
                              "format": "uuid",
                              "description": "The ejecutivo (auth user id) who asked."
                            },
                            "requested_at": {
                              "type": "string"
                            },
                            "note": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Why the ejecutivo asked."
                            },
                            "requested_percent": {
                              "type": "number",
                              "description": "The effective discount (percent over the list total) the approver was asked to allow. An approval covers any discount up to this figure."
                            },
                            "limit_percent": {
                              "type": "number",
                              "description": "The requester’s role limit when they asked."
                            },
                            "currency": {
                              "type": "string",
                              "enum": [
                                "UF",
                                "CLP"
                              ],
                              "description": "The currency the offer is written in."
                            },
                            "list_total": {
                              "type": "number"
                            },
                            "discount_total": {
                              "type": "number"
                            },
                            "total": {
                              "type": "number"
                            },
                            "decided_by": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "uuid"
                            },
                            "decided_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "decision_reason": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "description": "Always present on a refusal."
                            }
                          },
                          "required": [
                            "id",
                            "cotizacion_id",
                            "status",
                            "requested_by",
                            "requested_at",
                            "note",
                            "requested_percent",
                            "limit_percent",
                            "currency",
                            "list_total",
                            "discount_total",
                            "total",
                            "decided_by",
                            "decided_at",
                            "decision_reason"
                          ]
                        },
                        "cotizacion_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "cotizacion_display_id": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "approval",
                        "cotizacion_id",
                        "cotizacion_display_id"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "approval": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                      "status": "approved",
                      "requested_by": "0199e3a0-0000-7000-8000-0000000000bb",
                      "requested_at": "2026-10-20T12:00:00.000Z",
                      "note": "Cliente cierra hoy si llegamos al 6 %.",
                      "requested_percent": 6,
                      "limit_percent": 3,
                      "currency": "UF",
                      "list_total": 3510,
                      "discount_total": 210.6,
                      "total": 3299.4,
                      "decided_by": "0199e3a0-0000-7000-8000-0000000000cc",
                      "decided_at": "2026-10-20T12:10:00.000Z",
                      "decision_reason": null
                    },
                    "cotizacion_id": "0199e3a0-0000-7000-8000-000000000901",
                    "cotizacion_display_id": "CZ-12"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/cotizaciones/{id}/views": {
      "get": {
        "summary": "Who opened the cotización’s tracked link, and when",
        "description": "Every sent cotización carries a tracked link (it is in the message the buyer receives). Each person who opens the page or downloads its PDF through it leaves an anonymous event: a stable visitor number (1 is the first person; more than one means the link was passed on, `shared: true`), a device class and the time. No ip, no user agent, no identity. The ejecutivo is alerted in the bell on the first open, on a second visitor and on a return after a day. A draft has no events. Requires `leads:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. An ejecutivo only reaches the cotizaciones of their proyectos and leads; anything else is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The cotización’s id."
            },
            "required": true,
            "description": "The cotización’s id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "opens": {
                          "type": "integer"
                        },
                        "downloads": {
                          "type": "integer"
                        },
                        "visitors": {
                          "type": "integer",
                          "description": "Distinct visitors."
                        },
                        "shared": {
                          "type": "boolean",
                          "description": "More than one visitor: the link was passed on or opened on another device."
                        },
                        "first_opened_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "last_opened_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "events": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "kind": {
                                "type": "string",
                                "enum": [
                                  "open",
                                  "download"
                                ],
                                "description": "open: the page. download: its PDF."
                              },
                              "visitor_number": {
                                "type": "integer",
                                "description": "Stable position of the visitor: 1 is the first person to open the link. Anonymous (a cookie), never an identity."
                              },
                              "is_first": {
                                "type": "boolean",
                                "description": "The first event of anyone: the buyer opened it."
                              },
                              "is_new_visitor": {
                                "type": "boolean",
                                "description": "This visitor’s first event on the cotización."
                              },
                              "device": {
                                "type": "string",
                                "enum": [
                                  "mobile",
                                  "tablet",
                                  "desktop",
                                  "other"
                                ]
                              },
                              "occurred_at": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "id",
                              "kind",
                              "visitor_number",
                              "is_first",
                              "is_new_visitor",
                              "device",
                              "occurred_at"
                            ]
                          },
                          "description": "Newest first, at most 50."
                        }
                      },
                      "required": [
                        "opens",
                        "downloads",
                        "visitors",
                        "shared",
                        "first_opened_at",
                        "last_opened_at",
                        "events"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "opens": 3,
                    "downloads": 1,
                    "visitors": 2,
                    "shared": true,
                    "first_opened_at": "2026-10-20T15:02:00.000Z",
                    "last_opened_at": "2026-10-21T09:40:00.000Z",
                    "events": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b01",
                        "kind": "open",
                        "visitor_number": 1,
                        "is_first": true,
                        "is_new_visitor": true,
                        "device": "mobile",
                        "occurred_at": "2026-10-20T15:02:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/marketing-kit": {
      "get": {
        "summary": "Read a proyecto’s marketing kit",
        "description": "The proyecto’s kit (listing copy, Instagram posts, WhatsApp broadcast message) and the `source` facts it is written from, so a person’s own agent can write copy in another voice and send it back with PATCH. `kit` is null before the first generation. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. A proyecto outside the caller’s proyectos is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Kit and source",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MarketingKit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kit": null,
                    "source": {}
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Edit a proyecto’s marketing kit",
        "description": "Replaces whole sections (`listing`, `instagram`, `whatsapp`); the others stay. `whatsapp.message` is one paragraph of at most 500 characters (it fills a template variable). 409 `kit_missing` before the first generation. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. A proyecto outside the caller’s proyectos is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMarketingKitBody"
              },
              "example": {
                "whatsapp": {
                  "message": "Parque Los Aromos, en La Florida: en verde, entrega estimada marzo de 2027. Desde 2.890 UF. Escríbenos y coordinamos una visita a la sala de ventas."
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The kit now",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MarketingKitView"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {}
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/marketing-kit/generate": {
      "post": {
        "summary": "Generate a proyecto’s marketing kit",
        "description": "Writes the kit from the proyecto’s data and media: name, comuna, etapa, entrega, subsidios, each tipología with its m² and cheapest disponible UF price, photos and renders. No model is used; the copy arranges facts the inmobiliaria already keeps, in the workspace’s tú / usted register. Generating again REPLACES the content, edits included. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. A proyecto outside the caller’s proyectos is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The kit",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MarketingKit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "kit": {
                      "id": "0199e3a0-0000-7000-8000-000000000a01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "content": {
                        "listing": {
                          "headline": "Parque Los Aromos: departamentos en La Florida",
                          "summary": "Parque Los Aromos, en La Florida. En verde, entrega estimada marzo de 2027. Desde 2.890 UF.",
                          "body": "Departamentos pensados para vivir cerca del parque.\n\nTipologías disponibles:\n- 3D2B, 3 dormitorios, 2 baños, 78 m² desde 3.250 UF\n\nAgenda tu visita a la sala de ventas.",
                          "highlights": [
                            "Etapa: En verde",
                            "Entrega estimada marzo de 2027",
                            "Desde 2.890 UF"
                          ]
                        },
                        "instagram": [
                          {
                            "caption": "Conoce Parque Los Aromos, en La Florida. En verde, entrega estimada marzo de 2027. Desde 2.890 UF. Agenda tu visita a la sala de ventas.",
                            "hashtags": [
                              "parquelosaromos",
                              "laflorida",
                              "departamentos"
                            ],
                            "image_url": "https://media.example.com/aromos/fachada.jpg"
                          }
                        ],
                        "whatsapp": {
                          "message": "Parque Los Aromos, en La Florida: en verde, entrega estimada marzo de 2027. Desde 2.890 UF. Escríbenos y coordinamos una visita a la sala de ventas."
                        }
                      },
                      "generated_at": "2026-10-21T12:00:00.000Z",
                      "generated_by": null,
                      "edited_at": null,
                      "edited_by": null,
                      "created_at": "2026-10-21T12:00:00.000Z",
                      "updated_at": "2026-10-21T12:00:00.000Z"
                    },
                    "source": {}
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/marketing-kit/audience": {
      "get": {
        "summary": "Who a kit broadcast would reach",
        "description": "Counts only, no people: the leads about the proyecto that inquired through Vitrina, how many were already reached this week (one kit broadcast per contact per 7 days), how many opted out of marketing, who would receive it, and whether the approved `novedad_proyecto` template is on the number. Portal, import, manual and campaign leads are never included. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. A proyecto outside the caller’s proyectos is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Audience counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MarketingKitAudience"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "matched": 120,
                    "capped": 8,
                    "opted_out": 5,
                    "eligible": 107,
                    "ready": true,
                    "issue": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/marketing-kit/broadcast": {
      "post": {
        "summary": "Send a proyecto’s kit message by WhatsApp",
        "description": "Sends the kit’s `whatsapp.message` to the leads of `GET .../audience` as ONE paced marketing campaign with the approved `novedad_proyecto` template, so STOP, contact hours and Meta’s limits apply and the results show in Campañas. A person’s decision: it never runs by itself. 409 `kit_missing` (no kit), `no_channel` / `no_template` (WhatsApp or the approved template is missing) or `no_recipients`. Requires `campaigns:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`. A proyecto outside the caller’s proyectos is a 404.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The proyecto id."
            },
            "required": true,
            "description": "The proyecto id.",
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "The campaign",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MarketingKitBroadcast"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "campaign_id": "0199e3a0-0000-7000-8000-000000000c01",
                    "recipients": 107
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/real-estate-offers": {
      "get": {
        "summary": "List the unit offers sent to old leads",
        "description": "Each time a unidad that matches an old lead's criteria became available, dropped in price or was released after a reserva fell through, and the workspace had the offers on, the lead was offered it; this is that ledger, newest first. A caller whose role narrows proyectos sees the offers of their proyectos only. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Offers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UnitOffer"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [],
                  "meta": {
                    "pagination": {
                      "total": 0,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/real-estate-offers/settings": {
      "get": {
        "summary": "Read the unit-offer settings",
        "description": "Whether leads that inquired hear about matching stock, how many offers one contact may get per week, and how old an inquiry may be. Off by default. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "responses": {
          "200": {
            "description": "Settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitOfferSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enabled": false,
                    "weekly_cap": 1,
                    "max_inquiry_age_days": 365
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Change the unit-offer settings",
        "description": "The owner's decision to let old leads hear about matching stock (templates, STOP respected, capped per week). Only leads that inquired through Vitrina are ever offered a unidad, never portal or imported history. Turning it on starts offers for stock news from then on, with no backlog. Requires `campaigns:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateUnitOfferSettingsBody"
              },
              "example": {
                "enabled": true,
                "weekly_cap": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings now in force",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitOfferSettings"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "enabled": true,
                    "weekly_cap": 1,
                    "max_inquiry_age_days": 365
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/case": {
      "get": {
        "summary": "A post-venta caso: unidad, observaciones, fotos, visita, encuesta",
        "description": "The ticket seen as a caso de post-venta: its proyecto and unidad, every observación with its garantía (computed from the dates on record), the owner’s photos and videos (copied in from the caso’s conversations on read) and staff uploads, the next technical visita and the owner’s own 1 to 5 rating. `{id}` is the ticket uuid or its T-number. Requires `tickets:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The caso",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PostventaCase"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "display_id": "T-5",
                    "priority": "alta",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "display_id": "I-42",
                      "unit_number": "302",
                      "kind": "departamento"
                    },
                    "observations": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "partida": "instalaciones_sanitarias",
                        "description": "Filtración detrás del WC del baño principal, hace dos meses.",
                        "warranty_years": 5,
                        "warranty_base": "dom",
                        "warranty_start_at": "2024-03-15",
                        "warranty_expires_at": "2029-03-15",
                        "warranty_status": "dentro_de_plazo",
                        "needs_review": false,
                        "status": "open",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "resolved_at": null
                      }
                    ],
                    "media": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000f01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "observation_id": null,
                        "url": "https://media.example.com/conversations/abc/filtracion.jpg",
                        "media_type": "image",
                        "source": "conversation",
                        "message_id": null,
                        "caption": null,
                        "created_at": "2026-10-20T12:01:00.000Z"
                      }
                    ],
                    "next_visit": {
                      "appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "starts_at": "2026-10-22T13:00:00.000Z",
                      "ends_at": "2026-10-22T14:30:00.000Z",
                      "status": "confirmed"
                    },
                    "survey": {
                      "asked_at": null,
                      "score": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Set a caso’s priority or unidad",
        "description": "Staff set the priority (`emergencia` pushes the proyecto’s on-call people at once, bell + push) and/or the unidad. The unidad must be one the ticket’s contact owns (`unit_owner`), else 400; the proyecto follows from it. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateCaseBody"
              },
              "example": {
                "priority": "alta",
                "property_id": "0199e3a0-0000-7000-8000-000000000401"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated caso",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PostventaCase"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "display_id": "T-5",
                    "priority": "alta",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "display_id": "I-42",
                      "unit_number": "302",
                      "kind": "departamento"
                    },
                    "observations": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "partida": "instalaciones_sanitarias",
                        "description": "Filtración detrás del WC del baño principal, hace dos meses.",
                        "warranty_years": 5,
                        "warranty_base": "dom",
                        "warranty_start_at": "2024-03-15",
                        "warranty_expires_at": "2029-03-15",
                        "warranty_status": "dentro_de_plazo",
                        "needs_review": false,
                        "status": "open",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "resolved_at": null
                      }
                    ],
                    "media": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000f01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "observation_id": null,
                        "url": "https://media.example.com/conversations/abc/filtracion.jpg",
                        "media_type": "image",
                        "source": "conversation",
                        "message_id": null,
                        "caption": null,
                        "created_at": "2026-10-20T12:01:00.000Z"
                      }
                    ],
                    "next_visit": {
                      "appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "starts_at": "2026-10-22T13:00:00.000Z",
                      "ends_at": "2026-10-22T14:30:00.000Z",
                      "status": "confirmed"
                    },
                    "survey": {
                      "asked_at": null,
                      "score": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/observations": {
      "post": {
        "summary": "Add an observación (a defect) to a caso",
        "description": "One conversation can report several defects, so one caso holds several observaciones, each with its own partida and garantía. The plazo comes from the law (terminaciones 3 años from the unidad’s inscripción en el CBR; instalaciones and techumbre 5, estructura 10, from the proyecto’s recepción definitiva) and the dates on record: `dentro_de_plazo`, `fuera_de_plazo` or `por_revisar` (a date is missing). An out-of-plazo defect is still registered and flagged `needs_review`. 400 when the same partida and description are already on the caso. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateObservationBody"
              },
              "example": {
                "partida": "instalaciones_sanitarias",
                "description": "Filtración detrás del WC del baño principal."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Observación added",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TicketObservation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "partida": "instalaciones_sanitarias",
                    "description": "Filtración detrás del WC del baño principal, hace dos meses.",
                    "warranty_years": 5,
                    "warranty_base": "dom",
                    "warranty_start_at": "2024-03-15",
                    "warranty_expires_at": "2029-03-15",
                    "warranty_status": "dentro_de_plazo",
                    "needs_review": false,
                    "status": "open",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "resolved_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/observations/{observationId}": {
      "patch": {
        "summary": "Correct or resolve an observación",
        "description": "Changing the partida or the years recomputes the garantía. `status: resolved` stamps the resolution time. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The caso: ticket uuid or display id (T-5)."
            },
            "required": true,
            "description": "The caso: ticket uuid or display id (T-5).",
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "observationId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateObservationBody"
              },
              "example": {
                "status": "resolved"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The observación",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/TicketObservation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b01",
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "partida": "instalaciones_sanitarias",
                    "description": "Filtración detrás del WC del baño principal, hace dos meses.",
                    "warranty_years": 5,
                    "warranty_base": "dom",
                    "warranty_start_at": "2024-03-15",
                    "warranty_expires_at": "2029-03-15",
                    "warranty_status": "dentro_de_plazo",
                    "needs_review": false,
                    "status": "resolved",
                    "created_by": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "resolved_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/media": {
      "post": {
        "summary": "Attach photos or videos to a caso",
        "description": "Staff uploads (`source: staff`), optionally on one observación. Upload the file with `POST /tenant/media` first and send its `url`. A url already on the caso is left as it is. The owner’s own WhatsApp media attach on their own. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachCaseMediaBody"
              },
              "example": {
                "items": [
                  {
                    "url": "https://media.example.com/t/other/visita-1.jpg",
                    "observation_id": "0199e3a0-0000-7000-8000-000000000b01",
                    "caption": "Después de la reparación"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The caso with its gallery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PostventaCase"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "display_id": "T-5",
                    "priority": "alta",
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "display_id": "I-42",
                      "unit_number": "302",
                      "kind": "departamento"
                    },
                    "observations": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000b01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "partida": "instalaciones_sanitarias",
                        "description": "Filtración detrás del WC del baño principal, hace dos meses.",
                        "warranty_years": 5,
                        "warranty_base": "dom",
                        "warranty_start_at": "2024-03-15",
                        "warranty_expires_at": "2029-03-15",
                        "warranty_status": "dentro_de_plazo",
                        "needs_review": false,
                        "status": "open",
                        "created_by": null,
                        "created_at": "2026-10-20T12:00:00.000Z",
                        "resolved_at": null
                      }
                    ],
                    "media": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000f01",
                        "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                        "observation_id": null,
                        "url": "https://media.example.com/conversations/abc/filtracion.jpg",
                        "media_type": "image",
                        "source": "conversation",
                        "message_id": null,
                        "caption": null,
                        "created_at": "2026-10-20T12:01:00.000Z"
                      }
                    ],
                    "next_visit": {
                      "appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
                      "starts_at": "2026-10-22T13:00:00.000Z",
                      "ends_at": "2026-10-22T14:30:00.000Z",
                      "status": "confirmed"
                    },
                    "survey": {
                      "asked_at": null,
                      "score": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/visit": {
      "post": {
        "summary": "Schedule the technical visit of a caso",
        "description": "Books the visita as an appointment linked to the caso and tells the owner on the caso’s conversation (unless `notify_owner` is false). One live visit per caso: scheduling again cancels the previous one. The agent answers «¿cuándo viene el técnico?» from it. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleCaseVisitBody"
              },
              "example": {
                "starts_at": "2026-10-22T13:00:00.000Z",
                "ends_at": "2026-10-22T14:30:00.000Z"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Visit scheduled",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ScheduledCaseVisit"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "appointment_id": "0199e3a0-0000-7000-8000-000000000e01",
                    "starts_at": "2026-10-22T13:00:00.000Z",
                    "ends_at": "2026-10-22T14:30:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/postventa/unit-owners": {
      "get": {
        "summary": "The unidades a contact owns",
        "description": "The owner registry the agent identifies a WhatsApp number against: live ownerships of the contact, with the dates their garantías start from. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "The owner’s unidades."
            },
            "required": true,
            "description": "The owner’s unidades.",
            "name": "contact_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Unidades",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UnitOwner"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "unit_owner_id": "0199e3a0-0000-7000-8000-000000000d01",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "display_id": "I-42",
                      "unit_number": "302",
                      "kind": "departamento",
                      "title": "Departamento 302 · Vista Golf Departamentos",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "project_name": "Vista Golf Departamentos",
                      "escritura_at": "2024-04-02",
                      "cbr_registered_at": "2024-04-20",
                      "delivered_at": "2024-05-10",
                      "recepcion_definitiva_at": "2024-03-15"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Register the owner of a unidad",
        "description": "Idempotent on (unidad, contact): a second call updates the dates it names. `cbr_registered_at` starts the 3-year terminaciones garantía; the proyecto’s `recepcion_definitiva_at` starts the 5- and 10-year ones. The entrega of a negocio writes this row when negocios ship. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertUnitOwnerBody"
              },
              "example": {
                "property_id": "0199e3a0-0000-7000-8000-000000000401",
                "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                "escritura_at": "2024-04-02",
                "cbr_registered_at": "2024-04-20",
                "delivered_at": "2024-05-10"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Owner registered",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitOwner"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "unit_owner_id": "0199e3a0-0000-7000-8000-000000000d01",
                    "property_id": "0199e3a0-0000-7000-8000-000000000401",
                    "display_id": "I-42",
                    "unit_number": "302",
                    "kind": "departamento",
                    "title": "Departamento 302 · Vista Golf Departamentos",
                    "project_id": "0199e3a0-0000-7000-8000-000000000101",
                    "project_name": "Vista Golf Departamentos",
                    "escritura_at": "2024-04-02",
                    "cbr_registered_at": "2024-04-20",
                    "delivered_at": "2024-05-10",
                    "recepcion_definitiva_at": "2024-03-15"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/postventa/unit-owners/{id}": {
      "delete": {
        "summary": "End an ownership",
        "description": "The unidad was sold on, or the row was a mistake. Casos already opened keep their unidad. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Ended"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/postventa/report": {
      "get": {
        "summary": "Post-venta report by proyecto, partida, estado de garantía or etapa",
        "description": "Casos grouped by `group_by` (project | partida | warranty_status | stage | priority | contratista): casos, observaciones, open and resolved, observaciones outside their garantía, the median days to resolve, the oldest open caso and the owners’ average 1 to 5 rating. Only tickets that carry a proyecto or an observación count. `format=csv` returns one row per observación (casos without one included) as a UTF-8 CSV with BOM for a spreadsheet. Requires `tickets:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "project",
                "partida",
                "warranty_status",
                "stage",
                "priority",
                "contratista"
              ],
              "default": "project",
              "description": "project | partida | warranty_status | stage | priority | contratista."
            },
            "required": false,
            "description": "project | partida | warranty_status | stage | priority | contratista.",
            "name": "group_by",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "required": false,
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "json",
                "csv"
              ],
              "default": "json",
              "description": "csv = one row per observación (casos without one included), UTF-8 with BOM, for a spreadsheet."
            },
            "required": false,
            "description": "csv = one row per observación (casos without one included), UTF-8 with BOM, for a spreadsheet.",
            "name": "format",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Report rows (JSON), or the CSV when `format=csv`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PostventaReportRow"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "group_by": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "group_by"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "key": "0199e3a0-0000-7000-8000-000000000101",
                      "label": "Vista Golf Departamentos",
                      "casos": 9,
                      "observaciones": 11,
                      "abiertos": 4,
                      "resueltos": 5,
                      "fuera_de_plazo": 1,
                      "mediana_dias_resolucion": 7,
                      "antiguedad_max_dias": 12.5,
                      "encuesta_promedio": 4.4
                    }
                  ],
                  "meta": {
                    "group_by": "project"
                  }
                }
              },
              "text/csv": {
                "schema": {
                  "type": "string"
                },
                "example": "caso,proyecto,unidad,prioridad,etapa,estado,partida\r\n"
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/contratistas": {
      "get": {
        "summary": "List contratistas",
        "description": "The external companies and technicians that repair post-venta casos. Filters: `q` (name or trade), `trade`, `active`. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Name or trade contains."
            },
            "required": false,
            "description": "Name or trade contains.",
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 120
            },
            "required": false,
            "name": "trade",
            "in": "query"
          },
          {
            "schema": {
              "type": "boolean"
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Contratistas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contratista"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000a11",
                      "name": "Gasfitería Araucanía",
                      "trade": "gasfitería",
                      "whatsapp": "+56912345678",
                      "email": null,
                      "zones": [
                        "Ñuñoa",
                        "Providencia"
                      ],
                      "notes": null,
                      "active": true,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "updated_at": "2026-10-20T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a contratista",
        "description": "Names are unique among the workspace’s live contratistas (409 otherwise). `whatsapp` is stored as E.164; it is where the work and the link are sent. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContratistaBody"
              },
              "example": {
                "name": "Gasfitería Araucanía",
                "trade": "gasfitería",
                "whatsapp": "+56912345678",
                "zones": [
                  "Ñuñoa",
                  "Providencia"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contratista"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a11",
                    "name": "Gasfitería Araucanía",
                    "trade": "gasfitería",
                    "whatsapp": "+56912345678",
                    "email": null,
                    "zones": [
                      "Ñuñoa",
                      "Providencia"
                    ],
                    "notes": null,
                    "active": true,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/contratistas/{id}": {
      "get": {
        "summary": "One contratista",
        "description": "Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The contratista",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contratista"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a11",
                    "name": "Gasfitería Araucanía",
                    "trade": "gasfitería",
                    "whatsapp": "+56912345678",
                    "email": null,
                    "zones": [
                      "Ñuñoa",
                      "Providencia"
                    ],
                    "notes": null,
                    "active": true,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a contratista",
        "description": "Soft delete. Assignments already sent keep working until they finish. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Removed"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a contratista",
        "description": "Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContratistaBody"
              },
              "example": {
                "active": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contratista",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contratista"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a11",
                    "name": "Gasfitería Araucanía",
                    "trade": "gasfitería",
                    "whatsapp": "+56912345678",
                    "email": null,
                    "zones": [
                      "Ñuñoa",
                      "Providencia"
                    ],
                    "notes": null,
                    "active": true,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "updated_at": "2026-10-20T12:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/assignments": {
      "get": {
        "summary": "The contratistas a caso was sent to",
        "description": "Newest first, with the contratista’s name, trade and the observaciones sent. Requires `tickets:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Assignments",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseAssignment"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000a21",
                      "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                      "contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
                      "status": "assigned",
                      "brief": "Revisar la filtración detrás del WC.",
                      "owner_window": "Lunes a viernes de 9 a 18",
                      "appointment_id": null,
                      "visit_starts_at": null,
                      "notified_at": "2026-10-20T12:01:00.000Z",
                      "notify_error": null,
                      "accepted_at": null,
                      "scheduled_at": null,
                      "done_at": null,
                      "declined_at": null,
                      "decline_reason": null,
                      "completion_notes": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "contratista_name": "Gasfitería Araucanía",
                      "contratista_trade": "gasfitería",
                      "contratista_whatsapp": "+56912345678",
                      "observation_ids": [
                        "0199e3a0-0000-7000-8000-000000000b01"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Assign a caso (or some of its observaciones) to a contratista",
        "description": "Sends the caso to a contratista: they get a WhatsApp with a no-login link (`/caso/<token>`, 30 days) where they accept or decline, schedule the visit, upload before / after photos and close. Without `observation_ids` every open observación not already sent goes; an observación already with another live contratista is a 409. Assigning the same contratista again reuses the live assignment. The result says whether the WhatsApp went out (`notified`); a closed 24 h window or a missing number comes back in `notify_error`, and the `link` can be copied instead. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignCaseBody"
              },
              "example": {
                "contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
                "brief": "Revisar la filtración detrás del WC.",
                "owner_window": "Lunes a viernes de 9 a 18"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Assigned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AssignCaseResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "assignment": {
                      "id": "0199e3a0-0000-7000-8000-000000000a21",
                      "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                      "contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
                      "status": "assigned",
                      "brief": "Revisar la filtración detrás del WC.",
                      "owner_window": "Lunes a viernes de 9 a 18",
                      "appointment_id": null,
                      "visit_starts_at": null,
                      "notified_at": "2026-10-20T12:01:00.000Z",
                      "notify_error": null,
                      "accepted_at": null,
                      "scheduled_at": null,
                      "done_at": null,
                      "declined_at": null,
                      "decline_reason": null,
                      "completion_notes": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "contratista_name": "Gasfitería Araucanía",
                      "contratista_trade": "gasfitería",
                      "contratista_whatsapp": "+56912345678",
                      "observation_ids": [
                        "0199e3a0-0000-7000-8000-000000000b01"
                      ]
                    },
                    "link": "https://app.example.com/caso/eyJhbGciOi…",
                    "notified": true,
                    "notify_error": null,
                    "observation_ids": [
                      "0199e3a0-0000-7000-8000-000000000b01"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tickets/{id}/assignment-events": {
      "get": {
        "summary": "Every contratista step on a caso (timeline)",
        "description": "assigned, notified, viewed, accepted, declined, visit_scheduled, media_added, done, cancelled — newest first, with who did it (`staff`, `contratista`, `system`). The same steps also appear as system lines in the caso’s conversation thread. Requires `tickets:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tickets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaseAssignmentEvent"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000a31",
                      "assignment_id": "0199e3a0-0000-7000-8000-000000000a21",
                      "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                      "kind": "accepted",
                      "actor": "contratista",
                      "data": {},
                      "created_at": "2026-10-20T13:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/case-assignments/{id}/link": {
      "post": {
        "summary": "A fresh link for an assignment, optionally re-sent",
        "description": "Mints a new 30-day link for the assignment (the old one keeps working until it expires or the assignment is cancelled). With `resend: true` it is also sent to the contratista by WhatsApp. 409 when the assignment was cancelled. Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaseLinkBody"
              },
              "example": {
                "resend": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CaseLink"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "link": "https://app.example.com/caso/eyJhbGciOi…",
                    "notified": true,
                    "notify_error": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/case-assignments/{id}": {
      "delete": {
        "summary": "Cancel an assignment",
        "description": "The link stops working at once (it answers 404), the observaciones go back to the caso unassigned, and the contratista is told. A finished assignment cannot be cancelled (409). Requires `tickets:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The cancelled assignment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CaseAssignment"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000a21",
                    "ticket_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "contratista_id": "0199e3a0-0000-7000-8000-000000000a11",
                    "status": "cancelled",
                    "brief": "Revisar la filtración detrás del WC.",
                    "owner_window": "Lunes a viernes de 9 a 18",
                    "appointment_id": null,
                    "visit_starts_at": null,
                    "notified_at": "2026-10-20T12:01:00.000Z",
                    "notify_error": null,
                    "accepted_at": null,
                    "scheduled_at": null,
                    "done_at": null,
                    "declined_at": null,
                    "decline_reason": null,
                    "completion_notes": null,
                    "created_at": "2026-10-20T12:00:00.000Z",
                    "contratista_name": "Gasfitería Araucanía",
                    "contratista_trade": "gasfitería",
                    "contratista_whatsapp": "+56912345678",
                    "observation_ids": [
                      "0199e3a0-0000-7000-8000-000000000b01"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/handovers": {
      "get": {
        "summary": "List actas de entrega",
        "description": "Newest first, with the proyecto, the unidad, the owner and how many lines are observed / still unreviewed. Filters: `project_id`, `property_id`, `status`. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "project_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "property_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "draft",
                "pending_signature",
                "signed",
                "cancelled"
              ],
              "description": "draft (being walked) → pending_signature (sent to the owner) → signed; or cancelled."
            },
            "required": false,
            "description": "draft (being walked) → pending_signature (sent to the owner) → signed; or cancelled.",
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Actas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UnitHandoverListItem"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "pagination": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "limit": {
                              "type": "integer"
                            },
                            "offset": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "total",
                            "limit",
                            "offset"
                          ]
                        }
                      },
                      "required": [
                        "pagination"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z",
                      "project_name": "Vista Golf Departamentos",
                      "unit_label": "departamento 302",
                      "contact_name": "Marcela Ortiz",
                      "items_total": 30,
                      "items_observations": 1,
                      "items_pending": 3
                    }
                  ],
                  "meta": {
                    "pagination": {
                      "total": 1,
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Open the acta de entrega of a unidad",
        "description": "One live acta per unidad (409 `handover_id` of the existing one). The checklist is born with it: the proyecto’s own list (`/projects/{id}/entrega-checklist`) or the platform default, in walking order. The owner is the unidad’s registered owner or the `contact_id` given (and becomes the registered owner). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateHandoverBody"
              },
              "example": {
                "property_id": "0199e3a0-0000-7000-8000-000000000401",
                "contact_id": "0199e3a0-0000-7000-8000-000000000c01"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Acta opened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/handovers/{id}": {
      "get": {
        "summary": "One acta: checklist by section, photos and casos",
        "description": "Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The acta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Cancel an acta that is not signed",
        "description": "A signed acta is never cancelled (409). A cancelled one frees the unidad for a new acta. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Cancelled"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Write the acta’s general notes",
        "description": "Drafts only (409 otherwise). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHandoverBody"
              },
              "example": {
                "notes": "Se entregan 2 juegos de llaves."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The acta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/items/{itemId}": {
      "patch": {
        "summary": "Answer a checklist line",
        "description": "`state`: ok (conforme), observation (a defect), na or pending. An observation needs its `partida` (preselected from the line) and a `note`; `priority` and `split_case` only mean something on an observation (leaving the state resets them). Drafts only. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "itemId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateHandoverItemBody"
              },
              "example": {
                "state": "observation",
                "partida": "instalaciones_sanitarias",
                "note": "El lavamanos gotea por la base."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The line",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverItem"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000b31",
                    "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                    "section": "Baños",
                    "item_key": "bano_griferia",
                    "label": "Grifería, presión de agua y desagües",
                    "position": 12,
                    "state": "observation",
                    "partida": "instalaciones_sanitarias",
                    "priority": "normal",
                    "note": "El lavamanos gotea por la base.",
                    "split_case": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/media": {
      "post": {
        "summary": "Attach photos or videos to the walk",
        "description": "Upload the file with `POST /tenant/media` first and send its `url`, optionally on one line (`item_id`). A url already on the acta is left as it is. The photos of an observation travel to its caso when the acta is signed. Drafts only. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AttachHandoverMediaBody"
              },
              "example": {
                "items": [
                  {
                    "item_id": "0199e3a0-0000-7000-8000-000000000b31",
                    "url": "https://media.example.com/t/other/lavamanos.jpg",
                    "caption": "Gotea por la base"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The acta with its gallery",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/send": {
      "post": {
        "summary": "Send the acta to the owner to sign",
        "description": "Every line must be answered (400 otherwise, naming how many are pending). The acta is frozen and the owner gets a no-login link (`/acta/<token>`, 14 days) by WhatsApp text; `notified` says whether it went out (a closed 24 h window or a missing number comes back in `notify_error`). With `notify: false` only the link returns, to show on the staff device at the entrega. Calling it again on a pending acta mints a fresh link. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendHandoverBody"
              },
              "example": {
                "notify": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/SendHandoverResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "link": "https://app.example.com/acta/eyJhbGciOi…",
                    "notified": true,
                    "notify_error": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/reopen": {
      "post": {
        "summary": "Reopen an acta that was sent, to correct it",
        "description": "Back to draft; the owner’s link stops showing it until it is sent again. Only before it is signed. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The acta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/attest": {
      "post": {
        "summary": "Confirm the owner’s identity in person",
        "description": "For the entrega with the owner in front of staff (cédula in hand): the owner signs without a WhatsApp code for the next 12 hours, and the signature is recorded as `in_person` with who attested. Requires the acta to be sent (`pending_signature`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The acta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/UnitHandoverDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "handover": {
                      "id": "0199e3a0-0000-7000-8000-000000000b21",
                      "project_id": "0199e3a0-0000-7000-8000-000000000101",
                      "property_id": "0199e3a0-0000-7000-8000-000000000401",
                      "contact_id": "0199e3a0-0000-7000-8000-000000000c01",
                      "status": "draft",
                      "notes": null,
                      "performed_by": null,
                      "performed_at": "2026-10-20T12:00:00.000Z",
                      "signer_name": null,
                      "signed_at": null,
                      "signature_method": null,
                      "signed_pdf_sha256": null,
                      "signed_pdf_bytes": null,
                      "sent_for_signature_at": null,
                      "attested_at": null,
                      "cancelled_at": null,
                      "created_at": "2026-10-20T12:00:00.000Z"
                    },
                    "project": {
                      "id": "0199e3a0-0000-7000-8000-000000000101",
                      "name": "Vista Golf Departamentos"
                    },
                    "unit": {
                      "id": "0199e3a0-0000-7000-8000-000000000401",
                      "label": "departamento 302",
                      "address": "Av. Vicuña Mackenna 0000",
                      "comuna": "La Florida"
                    },
                    "owner": {
                      "id": "0199e3a0-0000-7000-8000-000000000c01",
                      "name": "Marcela Ortiz"
                    },
                    "sections": [
                      {
                        "title": "Baños",
                        "items": [
                          {
                            "id": "0199e3a0-0000-7000-8000-000000000b31",
                            "handover_id": "0199e3a0-0000-7000-8000-000000000b21",
                            "section": "Baños",
                            "item_key": "bano_griferia",
                            "label": "Grifería, presión de agua y desagües",
                            "position": 12,
                            "state": "observation",
                            "partida": "instalaciones_sanitarias",
                            "priority": "normal",
                            "note": "El lavamanos gotea por la base.",
                            "split_case": false
                          }
                        ]
                      }
                    ],
                    "media": [],
                    "casos": [],
                    "counts": {
                      "total": 30,
                      "ok": 24,
                      "observation": 1,
                      "na": 2,
                      "pending": 3
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/casos": {
      "post": {
        "summary": "Open the post-venta casos of a signed acta",
        "description": "Signing opens them already; this is the retry when that step failed. Idempotent: a line that already has its observación is skipped. The lines together open ONE caso with several observaciones; a line marked `split_case` opens its own. Each caso carries the unidad, the proyecto, the highest priority of its lines and the photos. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Casos opened (empty when there was nothing left to open)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OpenHandoverCasosResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "casos": [
                      {
                        "id": "0199e3a0-0000-7000-8000-000000000a01",
                        "display_id": "T-12",
                        "observations": 3
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/handovers/{id}/pdf": {
      "get": {
        "summary": "Download the signed acta",
        "description": "The sealed PDF, its SHA-256 re-verified on every read: an object that no longer matches what was signed is refused (409). 404 until the acta is signed. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The PDF",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/entrega-checklist": {
      "get": {
        "summary": "The proyecto’s entrega checklist",
        "description": "Its own list when it has one (`custom: true`), else the platform default. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EntregaChecklist"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "custom": false,
                    "sections": [
                      {
                        "key": "banos",
                        "title": "Baños",
                        "items": [
                          {
                            "key": "bano_artefactos",
                            "label": "WC, lavamanos y ducha",
                            "partida": "instalaciones_sanitarias"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Set the proyecto’s own entrega checklist",
        "description": "Replaces the list as a whole; the next acta of the proyecto is born from it (actas already open keep theirs). Section and item keys are unique. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PutEntregaChecklistBody"
              },
              "example": {
                "sections": [
                  {
                    "key": "banos",
                    "title": "Baños",
                    "items": [
                      {
                        "key": "bano_artefactos",
                        "label": "WC, lavamanos y ducha",
                        "partida": "instalaciones_sanitarias"
                      }
                    ]
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EntregaChecklist"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "custom": true,
                    "sections": [
                      {
                        "key": "banos",
                        "title": "Baños",
                        "items": [
                          {
                            "key": "bano_artefactos",
                            "label": "WC, lavamanos y ducha",
                            "partida": "instalaciones_sanitarias"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Back to the default entrega checklist",
        "description": "Requires `marketplace:write`. Only on a workspace that runs the Proyectos module (real-estate vertical, inmobiliaria); any other answers 403 `MODULE_NOT_ENABLED`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The default checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EntregaChecklist"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "custom": false,
                    "sections": [
                      {
                        "key": "banos",
                        "title": "Baños",
                        "items": [
                          {
                            "key": "bano_artefactos",
                            "label": "WC, lavamanos y ducha",
                            "partida": "instalaciones_sanitarias"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/negocios/{id}/buyer-view": {
      "get": {
        "summary": "What the buyer can be told on WhatsApp",
        "description": "The negocio exactly as the WhatsApp agent reads it for its buyer (`my_property`): stage, next deadline, entrega date, pending documents, the pie, the proyecto’s latest obra posts and, for unidades with a registered owner, the status of their post-venta casos. The agent reveals it only to the WhatsApp number of the negocio’s titular or cotitular (post-venta: of the unidad’s owner). Requires `leads:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The buyer’s view",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "identity": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "description": "The buyers (titular and cotitulares) whose WhatsApp number unlocks this view."
                        },
                        "negocios": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "negocio_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "display_id": {
                                "type": "string"
                              },
                              "project_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "stage": {
                                "type": "string"
                              },
                              "stage_label": {
                                "type": "string"
                              },
                              "units": {
                                "type": "array",
                                "items": {
                                  "type": "string"
                                }
                              },
                              "next_deadline": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "milestone": {
                                    "type": "string"
                                  },
                                  "label": {
                                    "type": "string"
                                  },
                                  "due_on": {
                                    "type": "string"
                                  },
                                  "overdue": {
                                    "type": "boolean"
                                  }
                                },
                                "required": [
                                  "milestone",
                                  "label",
                                  "due_on",
                                  "overdue"
                                ]
                              },
                              "entrega": {
                                "type": "object",
                                "properties": {
                                  "due_on": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  },
                                  "delivered_on": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "due_on",
                                  "delivered_on"
                                ]
                              },
                              "documents": {
                                "type": "object",
                                "properties": {
                                  "pending": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "key": {
                                          "type": "string"
                                        },
                                        "label": {
                                          "type": "string"
                                        },
                                        "status": {
                                          "type": "string"
                                        }
                                      },
                                      "required": [
                                        "key",
                                        "label",
                                        "status"
                                      ]
                                    }
                                  },
                                  "total_required": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "pending",
                                  "total_required"
                                ]
                              },
                              "pie": {
                                "type": [
                                  "object",
                                  "null"
                                ],
                                "properties": {
                                  "cuotas": {
                                    "type": "integer"
                                  },
                                  "cuotas_paid": {
                                    "type": "integer"
                                  },
                                  "paid_clp": {
                                    "type": "number"
                                  },
                                  "outstanding_clp": {
                                    "type": "number"
                                  },
                                  "overdue_clp": {
                                    "type": "number"
                                  },
                                  "next_due": {
                                    "type": [
                                      "object",
                                      "null"
                                    ],
                                    "properties": {
                                      "cuota": {
                                        "type": "integer"
                                      },
                                      "due_on": {
                                        "type": "string"
                                      },
                                      "amount_clp": {
                                        "type": "number"
                                      }
                                    },
                                    "required": [
                                      "cuota",
                                      "due_on",
                                      "amount_clp"
                                    ]
                                  }
                                },
                                "required": [
                                  "cuotas",
                                  "cuotas_paid",
                                  "paid_clp",
                                  "outstanding_clp",
                                  "overdue_clp",
                                  "next_due"
                                ]
                              }
                            },
                            "required": [
                              "negocio_id",
                              "display_id",
                              "project_name",
                              "stage",
                              "stage_label",
                              "units",
                              "next_deadline",
                              "entrega",
                              "documents",
                              "pie"
                            ]
                          }
                        },
                        "obra": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "project_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "project_name": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "title": {
                                "type": "string"
                              },
                              "note": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "posted_on": {
                                "type": "string"
                              },
                              "photos": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "id",
                              "project_id",
                              "project_name",
                              "title",
                              "note",
                              "posted_on",
                              "photos"
                            ]
                          }
                        },
                        "postventa": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "ticket_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "display_id": {
                                "type": "string"
                              },
                              "unit": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "stage": {
                                "type": "string"
                              },
                              "observations": {
                                "type": "integer"
                              },
                              "opened_on": {
                                "type": "string"
                              },
                              "resolved_on": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              }
                            },
                            "required": [
                              "ticket_id",
                              "display_id",
                              "unit",
                              "status",
                              "stage",
                              "observations",
                              "opened_on",
                              "resolved_on"
                            ]
                          }
                        }
                      },
                      "required": [
                        "identity",
                        "negocios",
                        "obra",
                        "postventa"
                      ],
                      "description": "What the WhatsApp agent can tell this negocio’s buyer, exactly as it reads it."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "identity": [
                      "Camila Rojas"
                    ],
                    "negocios": [
                      {
                        "negocio_id": "0199e3a0-0000-7000-8000-000000000b01",
                        "display_id": "NG-3",
                        "project_name": "Parque Los Aromos",
                        "stage": "promesado",
                        "stage_label": "Promesa firmada",
                        "units": [
                          "Departamento 604"
                        ],
                        "next_deadline": {
                          "milestone": "escritura",
                          "label": "firma de la escritura",
                          "due_on": "2026-12-15",
                          "overdue": false
                        },
                        "entrega": {
                          "due_on": "2027-03-30",
                          "delivered_on": null
                        },
                        "documents": {
                          "pending": [],
                          "total_required": 4
                        },
                        "pie": null
                      }
                    ],
                    "obra": [],
                    "postventa": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/obra-updates": {
      "get": {
        "summary": "A proyecto’s obra progress posts",
        "description": "Newest first. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Posts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "project_id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "title": {
                            "type": "string"
                          },
                          "note": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "photos": {
                            "type": "array",
                            "items": {
                              "type": "object",
                              "properties": {
                                "index": {
                                  "type": "integer"
                                },
                                "filename": {
                                  "type": "string"
                                },
                                "mime": {
                                  "type": "string"
                                },
                                "size": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "index",
                                "filename",
                                "mime",
                                "size"
                              ]
                            }
                          },
                          "posted_by": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "format": "uuid"
                          },
                          "notified_at": {
                            "type": [
                              "string",
                              "null"
                            ]
                          },
                          "notified_count": {
                            "type": "integer"
                          },
                          "created_at": {
                            "type": "string"
                          }
                        },
                        "required": [
                          "id",
                          "project_id",
                          "title",
                          "note",
                          "photos",
                          "posted_by",
                          "notified_at",
                          "notified_count",
                          "created_at"
                        ],
                        "description": "An obra progress post; photos are read at `…/photos/{index}`."
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3a0-0000-7000-8000-000000000e01",
                      "project_id": "0199e3a0-0000-7000-8000-000000000a01",
                      "title": "Terminaciones piso 6",
                      "note": "Se instalaron los pisos flotantes y se pintó el hall.",
                      "photos": [
                        {
                          "index": 0,
                          "filename": "hall.jpg",
                          "mime": "image/jpeg",
                          "size": 482113
                        }
                      ],
                      "posted_by": "0199e3a0-0000-7000-8000-000000000f01",
                      "notified_at": null,
                      "notified_count": 0,
                      "created_at": "2026-10-22T14:05:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Post an obra progress update",
        "description": "A title and a note; add photos with `…/photos` (up to 8), then tell the buyers with `…/announce`. Buyers can ask for the latest post and its photos on WhatsApp at any time. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 140,
                    "description": "e.g. «Terminaciones piso 6»."
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                },
                "required": [
                  "title"
                ],
                "additionalProperties": false
              },
              "example": {
                "title": "Terminaciones piso 6",
                "note": "Se instalaron los pisos flotantes."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "title": {
                          "type": "string"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "photos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "index": {
                                "type": "integer"
                              },
                              "filename": {
                                "type": "string"
                              },
                              "mime": {
                                "type": "string"
                              },
                              "size": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "index",
                              "filename",
                              "mime",
                              "size"
                            ]
                          }
                        },
                        "posted_by": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "notified_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "notified_count": {
                          "type": "integer"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "project_id",
                        "title",
                        "note",
                        "photos",
                        "posted_by",
                        "notified_at",
                        "notified_count",
                        "created_at"
                      ],
                      "description": "An obra progress post; photos are read at `…/photos/{index}`."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000e01",
                    "project_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "title": "Terminaciones piso 6",
                    "note": "Se instalaron los pisos flotantes y se pintó el hall.",
                    "photos": [
                      {
                        "index": 0,
                        "filename": "hall.jpg",
                        "mime": "image/jpeg",
                        "size": 482113
                      }
                    ],
                    "posted_by": "0199e3a0-0000-7000-8000-000000000f01",
                    "notified_at": null,
                    "notified_count": 0,
                    "created_at": "2026-10-22T14:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/obra-updates/{updateId}": {
      "delete": {
        "summary": "Remove an obra post",
        "description": "Buyers no longer see it. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "updateId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/projects/{id}/obra-updates/{updateId}/photos": {
      "post": {
        "summary": "Add a photo to an obra post",
        "description": "`multipart/form-data` with one `file` part (or the JSON file shape), JPG, PNG or WebP up to 10 MB; at most 8 photos per post. Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "updateId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "hall.jpg",
                "content": "/9j/4AAQSkZJRg…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The post with its photos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "title": {
                          "type": "string"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "photos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "index": {
                                "type": "integer"
                              },
                              "filename": {
                                "type": "string"
                              },
                              "mime": {
                                "type": "string"
                              },
                              "size": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "index",
                              "filename",
                              "mime",
                              "size"
                            ]
                          }
                        },
                        "posted_by": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "notified_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "notified_count": {
                          "type": "integer"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "project_id",
                        "title",
                        "note",
                        "photos",
                        "posted_by",
                        "notified_at",
                        "notified_count",
                        "created_at"
                      ],
                      "description": "An obra progress post; photos are read at `…/photos/{index}`."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000e01",
                    "project_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "title": "Terminaciones piso 6",
                    "note": "Se instalaron los pisos flotantes y se pintó el hall.",
                    "photos": [
                      {
                        "index": 0,
                        "filename": "hall.jpg",
                        "mime": "image/jpeg",
                        "size": 482113
                      }
                    ],
                    "posted_by": "0199e3a0-0000-7000-8000-000000000f01",
                    "notified_at": null,
                    "notified_count": 0,
                    "created_at": "2026-10-22T14:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/projects/{id}/obra-updates/{updateId}/photos/{index}": {
      "get": {
        "summary": "Read an obra photo",
        "description": "The image bytes. Requires `marketplace:read`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "updateId",
            "in": "path"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 7
            },
            "required": false,
            "name": "index",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Image",
            "content": {
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a photo from an obra post",
        "description": "Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "updateId",
            "in": "path"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0,
              "maximum": 7
            },
            "required": false,
            "name": "index",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The post without that photo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "project_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "title": {
                          "type": "string"
                        },
                        "note": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "photos": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "index": {
                                "type": "integer"
                              },
                              "filename": {
                                "type": "string"
                              },
                              "mime": {
                                "type": "string"
                              },
                              "size": {
                                "type": "integer"
                              }
                            },
                            "required": [
                              "index",
                              "filename",
                              "mime",
                              "size"
                            ]
                          }
                        },
                        "posted_by": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "notified_at": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "notified_count": {
                          "type": "integer"
                        },
                        "created_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "project_id",
                        "title",
                        "note",
                        "photos",
                        "posted_by",
                        "notified_at",
                        "notified_count",
                        "created_at"
                      ],
                      "description": "An obra progress post; photos are read at `…/photos/{index}`."
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3a0-0000-7000-8000-000000000e01",
                    "project_id": "0199e3a0-0000-7000-8000-000000000a01",
                    "title": "Terminaciones piso 6",
                    "note": "Se instalaron los pisos flotantes y se pintó el hall.",
                    "photos": [
                      {
                        "index": 0,
                        "filename": "hall.jpg",
                        "mime": "image/jpeg",
                        "size": 482113
                      }
                    ],
                    "posted_by": "0199e3a0-0000-7000-8000-000000000f01",
                    "notified_at": null,
                    "notified_count": 0,
                    "created_at": "2026-10-22T14:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/projects/{id}/obra-updates/{updateId}/announce": {
      "post": {
        "summary": "Tell the proyecto’s buyers about an obra post",
        "description": "A WhatsApp message to every buyer with a live negocio in the proyecto, inside Meta’s 24 h window only: a buyer whose window is closed is counted in `window_closed`, not messaged, and can still ask for the post. Once per post (409 `obra_already_announced`). Requires `marketplace:write`. Only on a workspace that runs the Proyectos module; an ejecutivo only reaches their proyectos (404 otherwise).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Projects"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "updateId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "buyers": {
                          "type": "integer",
                          "description": "Live negocios of the proyecto."
                        },
                        "sent": {
                          "type": "integer",
                          "description": "Messages delivered inside the 24 h window."
                        },
                        "window_closed": {
                          "type": "integer",
                          "description": "Buyers whose window is closed: not messaged, they can still ask."
                        },
                        "other": {
                          "type": "integer",
                          "description": "No WhatsApp thread, or a teammate is mid-chat."
                        }
                      },
                      "required": [
                        "buyers",
                        "sent",
                        "window_closed",
                        "other"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "buyers": 14,
                    "sent": 5,
                    "window_closed": 9,
                    "other": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/orders/checkout": {
      "post": {
        "summary": "Open a storefront guest checkout",
        "description": "A tenant’s own website (server side) sells with one call: the buyer (name, RUT, phone and/or email), the variants, the delivery method and optionally the payment provider in; an order and its payment link out (Mercado Pago by default, Webpay with `payment_provider: \"webpay\"`, or banca.me «cuotas sin tarjeta» with `payment_provider: \"bancame\"`, when the tenant connected it: see `GET /orders/checkout/payment-providers`). The buyer is found by email, then phone, or created; their RUT fills the contact’s empty one; an opt-in is recorded; the visit’s ad touch is kept for the Purchase. **Prices come from the catalog** (no price field); only native products.\n\n**Holds.** A variant with inventory policy `deny` («Solo con stock») holds the requested units for 15 minutes (50 with `bancame`): available = on hand − active holds, and the link stops taking money when the hold ends. With fewer units available the call is refused with 409 `insufficient_stock` (`details.available`). Two checkouts for the last unit: exactly one succeeds. A `continue` variant (made to order) never refuses and holds nothing. The hold ends when the payment settles (the sale: on hand goes down by the units), when the order is cancelled, or when it expires (the order is then cancelled and `order.status_changed` fires).\n\n**event_id** is the browser InitiateCheckout `eventID`: the server InitiateCheckout (when the buyer opens `checkout_url`) and the Purchase ship under it, so Meta deduplicates. It is also the idempotency key (200 + `replayed: true`).\n\nRefusals (`error.details.reason`): 400 `buyer_contact_required` · `invalid_phone` · `invalid_rut` · `shipping_quote_required` · `shipping_address_required` · `shipping_phone_required` · `shipping_quote_unknown` · `shipping_service_required` · `shipping_service_unavailable` · `unknown_item` · `inactive_item` · `no_price` · `variant_required`; 409 `insufficient_stock` · `store_item` · `event_id_reused` · `checkout_closed` · `already_paid` · `payment_not_connected` · `payment_provider_not_connected` · `payment_buyer_has_active_loan` (banca.me: the buyer already has a loan) · `shipping_quote_expired` · `shipping_quote_used` · `shipping_quote_items_mismatch` · `shipping_quote_destination_mismatch`; 502 `payment_provider_unavailable`. Fires `order.created`. Requires `orders:checkout`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_id": {
                    "type": "string",
                    "pattern": "^[A-Za-z0-9_.:-]{8,128}$",
                    "description": "The id the site gave its browser InitiateCheckout (Meta Pixel `eventID`), one per purchase attempt. The server-side InitiateCheckout and Purchase of this order ship under the same id, so Meta deduplicates browser and server. Also the idempotency key: the same `event_id` with the same items returns the same order and link (`replayed: true`); with other items it is refused (`event_id_reused`).",
                    "example": "ck_01JBX4W7Q2M5Z8"
                  },
                  "buyer": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 200
                      },
                      "rut": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 20,
                        "description": "Chilean RUT, any format (`12.345.678-5`). Stored canonical on the order; fills the contact’s empty RUT."
                      },
                      "phone": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 40,
                        "description": "Any Chilean format; stored E.164."
                      },
                      "email": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 320,
                        "format": "email"
                      }
                    },
                    "required": [
                      "name"
                    ],
                    "additionalProperties": false,
                    "description": "Who buys. Phone or email is required: the contact is found by email, then phone, or created."
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "variant_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 10,
                          "default": 1
                        }
                      },
                      "required": [
                        "variant_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 10,
                    "description": "What is bought: variants of NATIVE products, priced from the catalog (there is no price field)."
                  },
                  "delivery": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": "string",
                        "enum": [
                          "pickup",
                          "shipping"
                        ],
                        "description": "`pickup` (the buyer collects it at the store) or `shipping` (courier delivery: needs `quote_id` and `address`)."
                      },
                      "quote_id": {
                        "type": "string",
                        "format": "uuid",
                        "description": "`shipping`: a quote from `POST /orders/checkout/shipping-quotes` for exactly these items, not expired (30 minutes) and not used. Its price is the order’s `shipping_clp`, charged in the same payment."
                      },
                      "service_code": {
                        "type": "integer",
                        "description": "`shipping`: which of the quote’s `options` (its `service_code`). Required when the quote has more than one."
                      },
                      "recipient": {
                        "type": "object",
                        "properties": {
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 200
                          },
                          "phone": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 40
                          },
                          "email": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 320,
                            "format": "email"
                          }
                        },
                        "additionalProperties": false,
                        "description": "`shipping`: who receives the parcel. Each field defaults to the buyer’s. A phone is required (the buyer’s or this one)."
                      },
                      "address": {
                        "type": "object",
                        "properties": {
                          "street": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 100
                          },
                          "number": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 10
                          },
                          "apartment": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 200,
                            "description": "Depto, oficina, casa, block…"
                          }
                        },
                        "required": [
                          "street",
                          "number"
                        ],
                        "additionalProperties": false,
                        "description": "`shipping`: street and number in the quote’s commune."
                      },
                      "commune": {
                        "type": "string",
                        "maxLength": 120,
                        "description": "`shipping`, optional: the commune the buyer picked (name or code). Refused with `shipping_quote_destination_mismatch` when it is not the quote’s."
                      },
                      "region": {
                        "type": "string",
                        "maxLength": 120
                      },
                      "notes": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "`shipping`: instructions for the courier."
                      }
                    },
                    "required": [
                      "method"
                    ]
                  },
                  "consent": {
                    "type": "object",
                    "properties": {
                      "granted": {
                        "type": "boolean"
                      },
                      "source_url": {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      },
                      "text_version": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      }
                    },
                    "required": [
                      "granted",
                      "source_url",
                      "text_version"
                    ],
                    "additionalProperties": false,
                    "description": "The buyer’s opt-in to marketing messages as the site recorded it (Ley 21.719). Omit when the site asked nothing."
                  },
                  "attribution": {
                    "type": "object",
                    "properties": {
                      "utm_source": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "utm_medium": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "utm_campaign": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "utm_content": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "utm_term": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "utm_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "fbclid": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500
                      },
                      "gclid": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500
                      },
                      "anonymous_id": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 128,
                        "description": "The ad tracker’s anonymous visitor id, as read on the site."
                      },
                      "fbp": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 600,
                        "description": "Meta’s `_fbp` cookie, as read on the site."
                      },
                      "fbc": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 600,
                        "description": "Meta’s `_fbc` cookie, as read on the site."
                      }
                    },
                    "additionalProperties": false,
                    "description": "The visit’s ad touch. Malformed values are dropped, never refused."
                  },
                  "return_urls": {
                    "type": "object",
                    "properties": {
                      "success": {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      },
                      "failure": {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      },
                      "pending": {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      }
                    },
                    "required": [
                      "success",
                      "failure"
                    ],
                    "additionalProperties": false,
                    "description": "The site’s own pages the buyer returns to (`pending` defaults to `success`). Mercado Pago appends its own query (`collection_status`, `payment_id`, `external_reference` = the checkout id); Webpay returns through Vitrina, which commits the payment and then redirects here with `webpay_result=paid|rejected|cancelled|error` (`paid` → success, `rejected`/`cancelled` → failure, `error` → pending); banca.me also returns through Vitrina, which re-reads the loan and redirects here with `bancame_result=paid|pending|rejected|expired|error` (`paid` → success, `pending`/`error` → pending, `rejected`/`expired` → failure; `expired` also carries `bancame_reason=TOKEN_EXPIRED`). The query Vitrina appends is well-formed (`?` or `&` as the URL needs): banca.me’s own `?preApproveId=…` never reaches the site. Show the result from `GET /orders/checkout/{id}`, never from the query string."
                  },
                  "payment_provider": {
                    "type": "string",
                    "enum": [
                      "mercadopago",
                      "webpay",
                      "bancame"
                    ],
                    "description": "The provider the buyer chose, one the tenant connected (`GET /orders/checkout/payment-providers`). Absent = Mercado Pago (or the only one connected). Another one is refused with 409 `payment_provider_not_connected`. `bancame` («cuotas sin tarjeta») holds `deny` units for 50 minutes instead of 15 (the credit application takes longer). On a replay of the same `event_id`, a different provider points the same checkout at it."
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 1000
                  }
                },
                "required": [
                  "event_id",
                  "buyer",
                  "items",
                  "delivery",
                  "return_urls"
                ],
                "additionalProperties": false
              },
              "example": {
                "event_id": "ck_01JBX4W7Q2M5Z8",
                "buyer": {
                  "name": "Camila Rojas",
                  "rut": "12.345.678-5",
                  "phone": "+56 9 1234 5678",
                  "email": "camila@example.com"
                },
                "items": [
                  {
                    "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                    "quantity": 1
                  }
                ],
                "delivery": {
                  "method": "pickup"
                },
                "consent": {
                  "granted": true,
                  "source_url": "https://tienda.example.cl/checkout",
                  "text_version": "v1"
                },
                "attribution": {
                  "utm_source": "facebook",
                  "utm_medium": "paid",
                  "utm_campaign": "iphone14",
                  "fbclid": "IwAR0abc",
                  "anonymous_id": "anon_7f3c9a1b2d",
                  "fbp": "fb.1.1730000000000.1234567890"
                },
                "return_urls": {
                  "success": "https://tienda.example.cl/compra/ok",
                  "failure": "https://tienda.example.cl/compra/error",
                  "pending": "https://tienda.example.cl/compra/pendiente"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The same event_id again: the same order and link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GuestCheckout"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "order": {
                      "order_id": "0199e3b0-0000-7000-8000-000000000001",
                      "display_id": "O-42",
                      "status": "pending_payment",
                      "total_clp": 459990,
                      "currency": "CLP",
                      "lines": [
                        {
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                          "sku": "IP14-128-NEG",
                          "title": "iPhone 14 128 GB",
                          "variant_title": "Negro",
                          "options": [
                            {
                              "name": "Color",
                              "value": "Negro"
                            }
                          ],
                          "quantity": 1,
                          "unit_price_clp": 459990
                        }
                      ],
                      "delivery_method": "pickup",
                      "shipping_clp": 0,
                      "shipping": null,
                      "paid_at": null,
                      "cancelled_at": null,
                      "hold_expires_at": "2026-10-29T15:15:00.000Z",
                      "payment_failure": null,
                      "created_at": "2026-10-29T15:00:00.000Z"
                    },
                    "checkout": {
                      "checkout_id": "0199e3b0-0000-7000-8000-0000000000b1",
                      "checkout_url": "https://api.vitrinadev.com/p/0199e3b0-0000-7000-8000-0000000000b1",
                      "provider": "mercadopago",
                      "amount_clp": 459990,
                      "expires_at": "2026-10-29T15:15:00.000Z"
                    },
                    "hold": {
                      "expires_at": "2026-10-29T15:15:00.000Z",
                      "variant_ids": [
                        "0199e3b0-0000-7000-8000-0000000000f1"
                      ]
                    },
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "replayed": true
                  }
                }
              }
            }
          },
          "201": {
            "description": "The order, its link and its hold",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GuestCheckout"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "order": {
                      "order_id": "0199e3b0-0000-7000-8000-000000000001",
                      "display_id": "O-42",
                      "status": "pending_payment",
                      "total_clp": 459990,
                      "currency": "CLP",
                      "lines": [
                        {
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                          "sku": "IP14-128-NEG",
                          "title": "iPhone 14 128 GB",
                          "variant_title": "Negro",
                          "options": [
                            {
                              "name": "Color",
                              "value": "Negro"
                            }
                          ],
                          "quantity": 1,
                          "unit_price_clp": 459990
                        }
                      ],
                      "delivery_method": "pickup",
                      "shipping_clp": 0,
                      "shipping": null,
                      "paid_at": null,
                      "cancelled_at": null,
                      "hold_expires_at": "2026-10-29T15:15:00.000Z",
                      "payment_failure": null,
                      "created_at": "2026-10-29T15:00:00.000Z"
                    },
                    "checkout": {
                      "checkout_id": "0199e3b0-0000-7000-8000-0000000000b1",
                      "checkout_url": "https://api.vitrinadev.com/p/0199e3b0-0000-7000-8000-0000000000b1",
                      "provider": "mercadopago",
                      "amount_clp": 459990,
                      "expires_at": "2026-10-29T15:15:00.000Z"
                    },
                    "hold": {
                      "expires_at": "2026-10-29T15:15:00.000Z",
                      "variant_ids": [
                        "0199e3b0-0000-7000-8000-0000000000f1"
                      ]
                    },
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "replayed": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/orders/checkout/payment-providers": {
      "get": {
        "summary": "List the payment providers a storefront may offer",
        "description": "The providers the tenant connected that a storefront checkout can charge through (`mercadopago`, `webpay`, `bancame`), in the order to offer them, and the `default` used when `POST /orders/checkout` names none. Offer a choice only when there are two or more; pass the buyer’s pick as `payment_provider`. Requires `orders:checkout`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "responses": {
          "200": {
            "description": "The connected providers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/StorefrontPaymentProviders"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "default": "mercadopago",
                    "providers": [
                      {
                        "provider": "mercadopago",
                        "name": "Mercado Pago"
                      },
                      {
                        "provider": "webpay",
                        "name": "Webpay"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/checkout/{id}": {
      "get": {
        "summary": "Get a storefront checkout’s status",
        "description": "The status of an order opened by `POST /orders/checkout` (uuid or `O-42`), for the site’s return page: `pending_payment` (with `hold_expires_at`), `paid`, `cancelled` (expired or cancelled; `payment_failure` says when a banca.me application was rejected or expired, #6402). No buyer data. Any other order answers 404. Requires `orders:checkout`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The checkout’s order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/GuestCheckoutOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "order_id": "0199e3b0-0000-7000-8000-000000000001",
                    "display_id": "O-42",
                    "status": "pending_payment",
                    "total_clp": 459990,
                    "currency": "CLP",
                    "lines": [
                      {
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "sku": "IP14-128-NEG",
                        "title": "iPhone 14 128 GB",
                        "variant_title": "Negro",
                        "options": [
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "quantity": 1,
                        "unit_price_clp": 459990
                      }
                    ],
                    "delivery_method": "pickup",
                    "shipping_clp": 0,
                    "shipping": null,
                    "paid_at": null,
                    "cancelled_at": null,
                    "hold_expires_at": "2026-10-29T15:15:00.000Z",
                    "payment_failure": null,
                    "created_at": "2026-10-29T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders": {
      "get": {
        "summary": "List orders",
        "description": "«Pedidos»: every order of the workspace, newest first, with its lines and its derived `total_clp`. Filter by `status` and `source` (comma-separated), or by `contact_id` / `conversation_id` / `lead_id` for the orders next to a chat or a lead. Page with `meta.next_cursor`. Requires `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "draft",
                  "pending_payment",
                  "paid",
                  "preparing",
                  "ready_for_pickup",
                  "shipped",
                  "delivered",
                  "cancelled",
                  "refunded"
                ]
              },
              "minItems": 1,
              "description": "Comma-separated statuses.",
              "example": "pending_payment,paid"
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": [
                  "native",
                  "shopify",
                  "woocommerce"
                ]
              },
              "minItems": 1,
              "description": "Comma-separated sources.",
              "example": "native"
            },
            "required": false,
            "name": "source",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "contact_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "conversation_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "lead_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "maxLength": 200,
              "description": "The `meta.next_cursor` of the previous page."
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Orders",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CommerceOrder"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "next_cursor"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "0199e3b0-0000-7000-8000-000000000001",
                      "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                      "display_id": "O-42",
                      "source": "native",
                      "external_id": null,
                      "status": "pending_payment",
                      "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                      "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                      "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                      "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                      "currency": "CLP",
                      "delivery": {
                        "recipient_name": "Cliente de ejemplo",
                        "address": "Av. Ejemplo 123, depto 45",
                        "comuna": "Providencia",
                        "region": "Metropolitana"
                      },
                      "notes": null,
                      "shipping_clp": 0,
                      "total_clp": 39980,
                      "item_count": 2,
                      "stock_decremented_at": null,
                      "paid_at": null,
                      "ready_for_pickup_at": null,
                      "shipped_at": null,
                      "delivered_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null,
                      "refunded_at": null,
                      "refund_reason": null,
                      "refund_reference": null,
                      "buyer": {},
                      "attribution": {},
                      "client_event_id": null,
                      "return_urls": null,
                      "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-15T12:00:00.000Z",
                      "updated_at": "2026-10-15T12:00:00.000Z",
                      "lines": [
                        {
                          "id": "0199e3b0-0000-7000-8000-0000000000d1",
                          "position": 0,
                          "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                          "title": "Polera básica",
                          "variant_title": "M / Negro",
                          "variant_options": [
                            {
                              "name": "Talla",
                              "value": "M"
                            },
                            {
                              "name": "Color",
                              "value": "Negro"
                            }
                          ],
                          "sku": "POL-M-NEG",
                          "unit_price_clp": 9990,
                          "quantity": 2,
                          "line_total_clp": 19980,
                          "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                        },
                        {
                          "id": "0199e3b0-0000-7000-8000-0000000000d2",
                          "position": 1,
                          "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                          "title": "Polera básica",
                          "variant_title": "L / Blanco",
                          "variant_options": [
                            {
                              "name": "Talla",
                              "value": "L"
                            },
                            {
                              "name": "Color",
                              "value": "Blanco"
                            }
                          ],
                          "sku": "POL-L-BLA",
                          "unit_price_clp": 20000,
                          "quantity": 1,
                          "line_total_clp": 20000,
                          "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "next_cursor": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an order",
        "description": "A native order for a contact. **Prices come from the catalog**: each line names a `variant_id` (or the `product_id` of a product without variants) and a quantity, and its unit price is the variant’s price, else the product’s, frozen on the line. There is no price field. A line on an inactive item, an item without a CLP price, or more units than the store would sell (tracked stock, policy `deny`) is refused with 422 / 409 naming the line.\n\nWith `status: pending_payment` (the default) the payment opens in the same write: one obligation group of kind `order` (`obligation_group_id`) with one `product` obligation per line, so `POST /orders/{id}/payment-link` (or «Enviar link de pago» with `group_id`) charges it on Mercado Pago. When that payment settles the order becomes `paid` and its native stock is decremented once. The lead (`lead_id`, else the conversation’s open lead) moves to «Carrito» on an `ecommerce_orders` board. Fires `order.created`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "contact_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The buyer."
                  },
                  "conversation_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The conversation the sale happened in. Its open lead is used when `lead_id` is omitted."
                  },
                  "lead_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The lead this order closes. It moves along the `ecommerce_orders` board with the order."
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "variant_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "The variant to sell. Required when the product has variants; its price is the variant’s, else the product’s."
                        },
                        "product_id": {
                          "type": "string",
                          "format": "uuid",
                          "description": "A simple product (no variants). Ignored when `variant_id` is given."
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 10000
                        }
                      },
                      "required": [
                        "quantity"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "delivery": {
                    "type": "object",
                    "properties": {
                      "method": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "enum": [
                          "shipping",
                          "pickup"
                        ],
                        "description": "`shipping` (to `address`, `comuna`) or `pickup` (the buyer collects it at the store)."
                      },
                      "recipient_name": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      },
                      "phone": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 40
                      },
                      "address": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 500
                      },
                      "comuna": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "region": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 120
                      },
                      "notes": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 1000
                      }
                    },
                    "additionalProperties": false,
                    "description": "Where the order goes. Personal data of the buyer: erased with the contact."
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "draft",
                      "pending_payment"
                    ],
                    "description": "`pending_payment` (default) opens the payment now. `draft` freezes the lines and waits for `POST /orders/{id}/confirm`."
                  }
                },
                "required": [
                  "contact_id",
                  "lines"
                ],
                "additionalProperties": false
              },
              "example": {
                "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                "lines": [
                  {
                    "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                    "quantity": 2
                  },
                  {
                    "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                    "quantity": 1
                  }
                ],
                "delivery": {
                  "recipient_name": "Cliente de ejemplo",
                  "address": "Av. Ejemplo 123, depto 45",
                  "comuna": "Providencia",
                  "region": "Metropolitana"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The order, with its frozen lines",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommerceOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3b0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                    "display_id": "O-42",
                    "source": "native",
                    "external_id": null,
                    "status": "pending_payment",
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                    "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                    "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                    "currency": "CLP",
                    "delivery": {
                      "recipient_name": "Cliente de ejemplo",
                      "address": "Av. Ejemplo 123, depto 45",
                      "comuna": "Providencia",
                      "region": "Metropolitana"
                    },
                    "notes": null,
                    "shipping_clp": 0,
                    "total_clp": 39980,
                    "item_count": 2,
                    "stock_decremented_at": null,
                    "paid_at": null,
                    "ready_for_pickup_at": null,
                    "shipped_at": null,
                    "delivered_at": null,
                    "cancelled_at": null,
                    "cancelled_reason": null,
                    "refunded_at": null,
                    "refund_reason": null,
                    "refund_reference": null,
                    "buyer": {},
                    "attribution": {},
                    "client_event_id": null,
                    "return_urls": null,
                    "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-15T12:00:00.000Z",
                    "updated_at": "2026-10-15T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d1",
                        "position": 0,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "title": "Polera básica",
                        "variant_title": "M / Negro",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "M"
                          },
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "sku": "POL-M-NEG",
                        "unit_price_clp": 9990,
                        "quantity": 2,
                        "line_total_clp": 19980,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                      },
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d2",
                        "position": 1,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                        "title": "Polera básica",
                        "variant_title": "L / Blanco",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "L"
                          },
                          {
                            "name": "Color",
                            "value": "Blanco"
                          }
                        ],
                        "sku": "POL-L-BLA",
                        "unit_price_clp": 20000,
                        "quantity": 1,
                        "line_total_clp": 20000,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/orders/{id}": {
      "get": {
        "summary": "Get an order",
        "description": "One order with its lines and the buyer emails it sent (`buyer_emails`: kind, status, sent_at), by uuid or display id (`O-42`). `status` is the order’s lifecycle; whether money arrived is the ledger’s (`GET /obligations?group_id=…`). Requires `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommerceOrderDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3b0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                    "display_id": "O-42",
                    "source": "native",
                    "external_id": null,
                    "status": "paid",
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                    "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                    "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                    "currency": "CLP",
                    "delivery": {
                      "recipient_name": "Cliente de ejemplo",
                      "address": "Av. Ejemplo 123, depto 45",
                      "comuna": "Providencia",
                      "region": "Metropolitana"
                    },
                    "notes": null,
                    "shipping_clp": 0,
                    "total_clp": 39980,
                    "item_count": 2,
                    "stock_decremented_at": null,
                    "paid_at": null,
                    "ready_for_pickup_at": null,
                    "shipped_at": null,
                    "delivered_at": null,
                    "cancelled_at": null,
                    "cancelled_reason": null,
                    "refunded_at": null,
                    "refund_reason": null,
                    "refund_reference": null,
                    "buyer": {},
                    "attribution": {},
                    "client_event_id": null,
                    "return_urls": null,
                    "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-15T12:00:00.000Z",
                    "updated_at": "2026-10-15T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d1",
                        "position": 0,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "title": "Polera básica",
                        "variant_title": "M / Negro",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "M"
                          },
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "sku": "POL-M-NEG",
                        "unit_price_clp": 9990,
                        "quantity": 2,
                        "line_total_clp": 19980,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                      },
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d2",
                        "position": 1,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                        "title": "Polera básica",
                        "variant_title": "L / Blanco",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "L"
                          },
                          {
                            "name": "Color",
                            "value": "Blanco"
                          }
                        ],
                        "sku": "POL-L-BLA",
                        "unit_price_clp": 20000,
                        "quantity": 1,
                        "line_total_clp": 20000,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                      }
                    ],
                    "buyer_emails": [
                      {
                        "kind": "order_confirmed",
                        "status": "delivered",
                        "to": "comprador@example.com",
                        "sent_at": "2026-10-31T14:02:11.000Z",
                        "delivered_at": "2026-10-31T14:02:13.000Z",
                        "failed_at": null,
                        "attempts": 1,
                        "created_at": "2026-10-31T14:02:10.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/{id}/confirm": {
      "post": {
        "summary": "Confirm a draft order",
        "description": "`draft → pending_payment`: opens the order’s payment (its obligation group and one obligation per line) at the prices frozen when the draft was created. 409 `order_not_draft` for any other status. Fires `order.status_changed`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The confirmed order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommerceOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3b0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                    "display_id": "O-42",
                    "source": "native",
                    "external_id": null,
                    "status": "pending_payment",
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                    "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                    "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                    "currency": "CLP",
                    "delivery": {
                      "recipient_name": "Cliente de ejemplo",
                      "address": "Av. Ejemplo 123, depto 45",
                      "comuna": "Providencia",
                      "region": "Metropolitana"
                    },
                    "notes": null,
                    "shipping_clp": 0,
                    "total_clp": 39980,
                    "item_count": 2,
                    "stock_decremented_at": null,
                    "paid_at": null,
                    "ready_for_pickup_at": null,
                    "shipped_at": null,
                    "delivered_at": null,
                    "cancelled_at": null,
                    "cancelled_reason": null,
                    "refunded_at": null,
                    "refund_reason": null,
                    "refund_reference": null,
                    "buyer": {},
                    "attribution": {},
                    "client_event_id": null,
                    "return_urls": null,
                    "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-15T12:00:00.000Z",
                    "updated_at": "2026-10-15T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d1",
                        "position": 0,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "title": "Polera básica",
                        "variant_title": "M / Negro",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "M"
                          },
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "sku": "POL-M-NEG",
                        "unit_price_clp": 9990,
                        "quantity": 2,
                        "line_total_clp": 19980,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                      },
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d2",
                        "position": 1,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                        "title": "Polera básica",
                        "variant_title": "L / Blanco",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "L"
                          },
                          {
                            "name": "Color",
                            "value": "Blanco"
                          }
                        ],
                        "sku": "POL-L-BLA",
                        "unit_price_clp": 20000,
                        "quantity": 1,
                        "line_total_clp": 20000,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/{id}/payment-link": {
      "post": {
        "summary": "Get the order’s Mercado Pago link",
        "description": "The Checkout Pro link for what the order still owes, created from the workspace’s own connected Mercado Pago account and reused while the amount matches. Creating it states what is owed; it never marks anything paid. 409 `order_not_payable` unless the order is `pending_payment`, `not_connected` without Mercado Pago. To send it through the conversation use `POST /payment-links` with the order’s `obligation_group_id` as `group_id`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "checkout_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "checkout_url": {
                          "type": "string"
                        },
                        "amount_clp": {
                          "type": "integer"
                        },
                        "installments": {
                          "type": "boolean"
                        },
                        "receipt_url": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "obligation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "group_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "checkout_id",
                        "checkout_url",
                        "amount_clp",
                        "installments",
                        "receipt_url",
                        "obligation_id",
                        "group_id"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "checkout_id": "0199e3b0-0000-7000-8000-0000000000b1",
                    "checkout_url": "https://www.mercadopago.cl/checkout/v1/redirect?pref_id=123456789-abc",
                    "amount_clp": 39980,
                    "installments": true,
                    "receipt_url": "https://app.example.com/pago/0199e3b0-0000-7000-8000-0000000000b1",
                    "obligation_id": null,
                    "group_id": "0199e3b0-0000-7000-8000-000000000002"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/{id}/fulfilment": {
      "post": {
        "summary": "Move a paid order along fulfilment",
        "description": "Courier: `paid → preparing → shipped → delivered`. Pickup: `paid → preparing → ready_for_pickup → delivered` («Listo para retiro»). A step may be skipped (paid → shipped, paid → delivered). An unpaid order answers 409 `invalid_transition`; `ready_for_pickup` on a courier order 409 `not_a_pickup_order`; `shipped` on a pickup order 409 `pickup_order_not_shipped`. A storefront buyer is emailed once per step (ready_for_pickup, shipped, delivered; the confirmation goes out when it is paid), and a failed email never fails the move. The lead follows on an `ecommerce_orders` board (Despachado, Postventa). Fires `order.status_changed`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "preparing",
                      "ready_for_pickup",
                      "shipped",
                      "delivered"
                    ],
                    "description": "The next fulfilment step. A paid order may skip steps (paid → shipped). `ready_for_pickup` («Listo para retiro») only for `delivery.method = pickup` (else 409 `not_a_pickup_order`); a pickup order is never `shipped` (409 `pickup_order_not_shipped`). Each of paid, ready_for_pickup, shipped and delivered emails a storefront buyer once."
                  }
                },
                "required": [
                  "status"
                ],
                "additionalProperties": false
              },
              "example": {
                "status": "shipped"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommerceOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3b0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                    "display_id": "O-42",
                    "source": "native",
                    "external_id": null,
                    "status": "shipped",
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                    "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                    "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                    "currency": "CLP",
                    "delivery": {
                      "recipient_name": "Cliente de ejemplo",
                      "address": "Av. Ejemplo 123, depto 45",
                      "comuna": "Providencia",
                      "region": "Metropolitana"
                    },
                    "notes": null,
                    "shipping_clp": 0,
                    "total_clp": 39980,
                    "item_count": 2,
                    "stock_decremented_at": null,
                    "paid_at": null,
                    "ready_for_pickup_at": null,
                    "shipped_at": null,
                    "delivered_at": null,
                    "cancelled_at": null,
                    "cancelled_reason": null,
                    "refunded_at": null,
                    "refund_reason": null,
                    "refund_reference": null,
                    "buyer": {},
                    "attribution": {},
                    "client_event_id": null,
                    "return_urls": null,
                    "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-15T12:00:00.000Z",
                    "updated_at": "2026-10-15T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d1",
                        "position": 0,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "title": "Polera básica",
                        "variant_title": "M / Negro",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "M"
                          },
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "sku": "POL-M-NEG",
                        "unit_price_clp": 9990,
                        "quantity": 2,
                        "line_total_clp": 19980,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                      },
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d2",
                        "position": 1,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                        "title": "Polera básica",
                        "variant_title": "L / Blanco",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "L"
                          },
                          {
                            "name": "Color",
                            "value": "Blanco"
                          }
                        ],
                        "sku": "POL-L-BLA",
                        "unit_price_clp": 20000,
                        "quantity": 1,
                        "line_total_clp": 20000,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/{id}/cancel": {
      "post": {
        "summary": "Cancel an unpaid order",
        "description": "Cancels a `draft` or `pending_payment` order: its obligations and its group are cancelled and its Mercado Pago link stops taking money (a payment already in flight is credited to the customer, applied to nothing). An order that already received money answers 409 `order_has_payments`, a paid one 409 `order_paid`: giving money back is a refund on the payments ledger. Cancelling twice returns the cancelled order. Fires `order.status_changed`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "example": "El cliente se arrepintió antes de pagar"
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El cliente se arrepintió antes de pagar"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The cancelled order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CommerceOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0199e3b0-0000-7000-8000-000000000001",
                    "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                    "display_id": "O-42",
                    "source": "native",
                    "external_id": null,
                    "status": "cancelled",
                    "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                    "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                    "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                    "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                    "currency": "CLP",
                    "delivery": {
                      "recipient_name": "Cliente de ejemplo",
                      "address": "Av. Ejemplo 123, depto 45",
                      "comuna": "Providencia",
                      "region": "Metropolitana"
                    },
                    "notes": null,
                    "shipping_clp": 0,
                    "total_clp": 39980,
                    "item_count": 2,
                    "stock_decremented_at": null,
                    "paid_at": null,
                    "ready_for_pickup_at": null,
                    "shipped_at": null,
                    "delivered_at": null,
                    "cancelled_at": "2026-10-15T12:30:00.000Z",
                    "cancelled_reason": "El cliente se arrepintió antes de pagar",
                    "refunded_at": null,
                    "refund_reason": null,
                    "refund_reference": null,
                    "buyer": {},
                    "attribution": {},
                    "client_event_id": null,
                    "return_urls": null,
                    "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                    "created_at": "2026-10-15T12:00:00.000Z",
                    "updated_at": "2026-10-15T12:00:00.000Z",
                    "lines": [
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d1",
                        "position": 0,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                        "title": "Polera básica",
                        "variant_title": "M / Negro",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "M"
                          },
                          {
                            "name": "Color",
                            "value": "Negro"
                          }
                        ],
                        "sku": "POL-M-NEG",
                        "unit_price_clp": 9990,
                        "quantity": 2,
                        "line_total_clp": 19980,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                      },
                      {
                        "id": "0199e3b0-0000-7000-8000-0000000000d2",
                        "position": 1,
                        "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                        "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                        "title": "Polera básica",
                        "variant_title": "L / Blanco",
                        "variant_options": [
                          {
                            "name": "Talla",
                            "value": "L"
                          },
                          {
                            "name": "Color",
                            "value": "Blanco"
                          }
                        ],
                        "sku": "POL-L-BLA",
                        "unit_price_clp": 20000,
                        "quantity": 1,
                        "line_total_clp": 20000,
                        "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/orders/{id}/refund": {
      "post": {
        "summary": "Refund a paid order in full",
        "description": "Gives a paid native order back in full (`paid`, `preparing`, `shipped` or `delivered`): retracto, a defect, a unit that could not be delivered. The refund runs at the provider the order was paid with, the workspace's own Mercado Pago, Webpay Plus (a reversal the same day, an annulment later) or banca.me (the loan is cancelled and banca.me returns any installment already paid; only within 60 days of the disbursement), against the order's payment (idempotent per order: a retry never refunds twice), the payment is reversed in the ledger and the ads engine nets the Purchase, the order becomes `refunded` and `order.status_changed` fires with `changes.status.to = refunded` (its `data.payment` names the refunded payment and its provider; `data.refunded_at` and `data.refund_reason` are set). The sold units go back on hand only when the workspace's own stock is Vitrina-managed (`restored_units`); when another system writes on-hand counts (a back office syncing absolute stock) nothing is added: that system reacts to the event. Refunding a refunded order answers 200 with `refunded: false`. Errors: 409 `order_not_paid` (draft, pending payment or cancelled), 409 `payment_not_refundable` (not paid through Mercado Pago, Webpay or banca.me, e.g. a recorded transfer), 409 `refund_not_supported`, 409 `payment_not_connected`, 409 `store_order`; 502 `refund_refused` (the provider said no: past the refund window, e.g. banca.me after 60 days, a transaction not committed) or `payment_provider_unavailable`. On a 502 the order is unchanged and the call may be retried. Requires `orders:refund` (staff only; it is not part of any agent or storefront key).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "description": "Why the order is given back (retracto, defect, unit not deliverable). Kept on the order and in the audit log.",
                    "example": "Retracto: el cliente se arrepintió dentro de los 10 días"
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "Retracto: el cliente se arrepintió dentro de los 10 días"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The refunded order",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/OrderRefundResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "order": {
                      "id": "0199e3b0-0000-7000-8000-000000000001",
                      "tenant_id": "0199e3b0-0000-7000-8000-0000000000aa",
                      "display_id": "O-42",
                      "source": "native",
                      "external_id": null,
                      "status": "refunded",
                      "contact_id": "0199e3b0-0000-7000-8000-0000000000c1",
                      "conversation_id": "0199e3b0-0000-7000-8000-0000000000c2",
                      "lead_id": "0199e3b0-0000-7000-8000-0000000000c3",
                      "obligation_group_id": "0199e3b0-0000-7000-8000-000000000002",
                      "currency": "CLP",
                      "delivery": {
                        "recipient_name": "Cliente de ejemplo",
                        "address": "Av. Ejemplo 123, depto 45",
                        "comuna": "Providencia",
                        "region": "Metropolitana"
                      },
                      "notes": null,
                      "shipping_clp": 0,
                      "total_clp": 39980,
                      "item_count": 2,
                      "stock_decremented_at": null,
                      "paid_at": "2026-10-15T12:05:00.000Z",
                      "ready_for_pickup_at": null,
                      "shipped_at": null,
                      "delivered_at": null,
                      "cancelled_at": null,
                      "cancelled_reason": null,
                      "refunded_at": "2026-10-16T09:00:00.000Z",
                      "refund_reason": "Retracto: el cliente se arrepintió dentro de los 10 días",
                      "refund_reference": "1318847654",
                      "buyer": {},
                      "attribution": {},
                      "client_event_id": null,
                      "return_urls": null,
                      "created_by": "0199e3b0-0000-7000-8000-0000000000bb",
                      "created_at": "2026-10-15T12:00:00.000Z",
                      "updated_at": "2026-10-15T12:00:00.000Z",
                      "lines": [
                        {
                          "id": "0199e3b0-0000-7000-8000-0000000000d1",
                          "position": 0,
                          "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                          "title": "Polera básica",
                          "variant_title": "M / Negro",
                          "variant_options": [
                            {
                              "name": "Talla",
                              "value": "M"
                            },
                            {
                              "name": "Color",
                              "value": "Negro"
                            }
                          ],
                          "sku": "POL-M-NEG",
                          "unit_price_clp": 9990,
                          "quantity": 2,
                          "line_total_clp": 19980,
                          "obligation_id": "0199e3b0-0000-7000-8000-0000000000a1"
                        },
                        {
                          "id": "0199e3b0-0000-7000-8000-0000000000d2",
                          "position": 1,
                          "product_id": "0199e3b0-0000-7000-8000-0000000000e1",
                          "variant_id": "0199e3b0-0000-7000-8000-0000000000f2",
                          "title": "Polera básica",
                          "variant_title": "L / Blanco",
                          "variant_options": [
                            {
                              "name": "Talla",
                              "value": "L"
                            },
                            {
                              "name": "Color",
                              "value": "Blanco"
                            }
                          ],
                          "sku": "POL-L-BLA",
                          "unit_price_clp": 20000,
                          "quantity": 1,
                          "line_total_clp": 20000,
                          "obligation_id": "0199e3b0-0000-7000-8000-0000000000a2"
                        }
                      ]
                    },
                    "refunded": true,
                    "restored_units": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/orders/checkout/shipping-communes": {
      "get": {
        "summary": "List the communes the store ships to",
        "description": "Every commune (and locality) the store’s courier (Chilexpress) delivers to, with the `coverage_code` a quote takes. Cached 12 hours. 409 `shipping_not_connected` when the store has no courier connected. Requires `orders:checkout` (a storefront’s key) or `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "responses": {
          "200": {
            "description": "The communes, by region then name",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ShippingCommune"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "coverage_code": "CONC",
                      "name": "Concepcion",
                      "commune": "Concepcion",
                      "region_code": "R8",
                      "region": "Biobio",
                      "kind": "commune"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/orders/checkout/shipping-quotes": {
      "post": {
        "summary": "Quote courier delivery for a checkout",
        "description": "The price (and an estimated transit in business days) of delivering these items to a commune, per service the store offers, cheapest first. The package is the store’s unit box × the quantity; the declared value is the items’ catalog price. The quote is kept **30 minutes**: send its `quote_id` (and a `service_code` when there are several options) in `POST /orders/checkout` with `delivery.method = shipping`; the price is charged with the products in the same payment. The caller never states a price.\n\nRefusals (`error.details.reason`): 400 `unknown_commune` · `commune_required` · `unknown_item` · `inactive_item` · `no_price`; 409 `shipping_not_connected` · `no_shipping_service` · `insufficient_stock` · `shipping_key_rejected`; 502 `shipping_provider_unavailable`. Requires `orders:checkout` (a storefront’s key) or `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "variant_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 10,
                          "default": 1
                        }
                      },
                      "required": [
                        "variant_id"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "maxItems": 10,
                    "description": "The items the buyer is about to check out (the same list the checkout will send)."
                  },
                  "destination": {
                    "type": "object",
                    "properties": {
                      "coverage_code": {
                        "type": "string",
                        "maxLength": 6,
                        "description": "A code from `GET /orders/checkout/shipping-communes` (preferred)."
                      },
                      "commune": {
                        "type": "string",
                        "maxLength": 120,
                        "description": "Or the commune name (accents ignored)."
                      },
                      "region_code": {
                        "type": "string",
                        "maxLength": 4
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "items",
                  "destination"
                ],
                "additionalProperties": false
              },
              "example": {
                "items": [
                  {
                    "variant_id": "0199e3b0-0000-7000-8000-0000000000f1",
                    "quantity": 1
                  }
                ],
                "destination": {
                  "coverage_code": "CONC"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The quote",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ShippingQuote"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "quote_id": "0199e3b0-0000-7000-8000-0000000000a9",
                    "carrier": "chilexpress",
                    "destination": {
                      "coverage_code": "CONC",
                      "commune": "Concepcion",
                      "region_code": "R8",
                      "region": "Biobio"
                    },
                    "options": [
                      {
                        "service_code": 3,
                        "service_name": "Express",
                        "price_clp": 6424,
                        "estimated_business_days": 2
                      }
                    ],
                    "expires_at": "2026-10-29T15:30:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/orders/{id}/shipment": {
      "get": {
        "summary": "Get an order’s shipment and label",
        "description": "The courier shipment of an order: number, public tracking link, service, and the label to print (`label.base64`, a JPEG), or null once erased. 404 before dispatch. Requires `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The shipment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/CommerceShipment"
                        },
                        {
                          "type": "object",
                          "properties": {
                            "label": {
                              "type": [
                                "object",
                                "null"
                              ],
                              "properties": {
                                "content_type": {
                                  "type": "string",
                                  "enum": [
                                    "image/jpeg"
                                  ]
                                },
                                "base64": {
                                  "type": "string"
                                }
                              },
                              "required": [
                                "content_type",
                                "base64"
                              ]
                            }
                          },
                          "required": [
                            "label"
                          ]
                        }
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "carrier": "chilexpress",
                    "environment": "test",
                    "shipment_number": "712678918615",
                    "tracking_url": "https://centrodeayuda.chilexpress.cl/seguimiento/712678918615",
                    "service_code": 3,
                    "service_name": "EXPRESS",
                    "reference": "O-42",
                    "has_label": true,
                    "created_at": "2026-10-30T12:00:00.000Z",
                    "label": {
                      "content_type": "image/jpeg",
                      "base64": "/9j/4AAQSkZJRgABAQ…"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Dispatch an order by courier",
        "description": "«Despachar»: creates the shipment (transport order) at Chilexpress for a paid order with `delivery.method = shipping`, stores its number, its public tracking link and its label (`GET /orders/{id}/shipment`), writes `delivery.shipment` on the order and moves it to `shipped` (fires `order.status_changed`). Dispatching again returns the existing shipment (200, `created: false`).\n\nRefusals (`error.details.reason`): 409 `not_a_shipping_order` · `order_not_dispatchable` · `shipping_address_incomplete` · `shipping_not_connected` · `shipping_dispatch_not_configured` · `shipping_key_rejected` · `store_order`; 400 `shipping_rejected` (Chilexpress refused the data; `details.provider_errors`); 502 `shipping_provider_unavailable`. Requires `orders:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "description": "The order uuid or its display id (`O-12`).",
              "example": "O-42"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "service_code": {
                    "type": "integer",
                    "description": "Override the service the buyer chose (default: the order’s)."
                  }
                },
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already dispatched: the existing shipment",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "order": {
                          "$ref": "#/components/schemas/CommerceOrder"
                        },
                        "shipment": {
                          "$ref": "#/components/schemas/CommerceShipment"
                        },
                        "created": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "order",
                        "shipment",
                        "created"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "shipment": {
                      "carrier": "chilexpress",
                      "environment": "test",
                      "shipment_number": "712678918615",
                      "tracking_url": "https://centrodeayuda.chilexpress.cl/seguimiento/712678918615",
                      "service_code": 3,
                      "service_name": "EXPRESS",
                      "reference": "O-42",
                      "has_label": true,
                      "created_at": "2026-10-30T12:00:00.000Z"
                    },
                    "created": false
                  }
                }
              }
            }
          },
          "201": {
            "description": "Dispatched",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "order": {
                          "$ref": "#/components/schemas/CommerceOrder"
                        },
                        "shipment": {
                          "$ref": "#/components/schemas/CommerceShipment"
                        },
                        "created": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "order",
                        "shipment",
                        "created"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "shipment": {
                      "carrier": "chilexpress",
                      "environment": "test",
                      "shipment_number": "712678918615",
                      "tracking_url": "https://centrodeayuda.chilexpress.cl/seguimiento/712678918615",
                      "service_code": 3,
                      "service_name": "EXPRESS",
                      "reference": "O-42",
                      "has_label": true,
                      "created_at": "2026-10-30T12:00:00.000Z"
                    },
                    "created": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/shipping/chilexpress": {
      "get": {
        "summary": "Get the store’s Chilexpress connection",
        "description": "The store’s own Chilexpress account as connected: environment, origin commune, TCC, sender, return address, default package, offered services, and masked key previews (never the keys). `connected: false` when none. Requires `tenant:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChilexpressConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "test",
                    "origin_coverage_code": "PROV",
                    "origin_commune": "Providencia",
                    "origin_region_code": "RM",
                    "customer_card_number": "18578680",
                    "keys": {
                      "rating": "a1b2••••9f",
                      "shipments": "c3d4••••0e",
                      "coverage": null
                    },
                    "can_dispatch": true,
                    "sender": {
                      "name": "Tienda Ejemplo",
                      "phone": "+56912345678",
                      "email": "ventas@tienda.example.cl"
                    },
                    "return_address": {
                      "street": "Av. Ejemplo",
                      "number": "123",
                      "supplement": "Oficina 5"
                    },
                    "package": {
                      "weight_kg": 0.6,
                      "height_cm": 8,
                      "width_cm": 12,
                      "length_cm": 20
                    },
                    "services": [
                      3
                    ],
                    "declared_content": "celular",
                    "seller_rut": null,
                    "connected_at": "2026-10-29T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Connect or update the store’s Chilexpress account",
        "description": "Saves the store’s own Chilexpress account (developers.wschilexpress.com subscription keys, Tarjeta Cliente Chilexpress, origin commune, sender, return address, default package). The Cotizador key is verified with a real quote first; keys are stored encrypted and never returned. Keys not sent are kept. The Envíos key is proven on the first dispatch. Production keys may need a commercial agreement with Chilexpress.\n\nRefusals (`error.details.reason`): 400 `invalid_chilexpress_settings` (`details.field`) · `chilexpress_key_rejected` · `unknown_commune`; 502 `shipping_provider_unavailable`. Requires `tenant:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "environment": {
                    "type": "string",
                    "enum": [
                      "test",
                      "production"
                    ],
                    "description": "`test` = testservices.wschilexpress.com (self-service keys), `production` = services.wschilexpress.com."
                  },
                  "rating_key": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 200,
                    "description": "Primary key of the «Cotizador» subscription (developers.wschilexpress.com → Perfil). Required on the first connect; omit to keep the stored one."
                  },
                  "shipments_key": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Primary key of the «Envíos» subscription: needed to dispatch. Omit to keep; null or \"\" clears it."
                  },
                  "coverage_key": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200,
                    "description": "Primary key of the «Coberturas» subscription. Optional: the Cotizador key is used when absent."
                  },
                  "customer_card_number": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{1,12}$",
                    "description": "Tarjeta Cliente Chilexpress (TCC): the account shipments are billed to. Needed to dispatch. Test: 18578680.",
                    "example": "18578680"
                  },
                  "origin_commune": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120,
                    "description": "The commune the store ships from: a name (`Providencia`) or a Chilexpress coverage code (`PROV`).",
                    "example": "Providencia"
                  },
                  "origin_region_code": {
                    "type": "string",
                    "maxLength": 4,
                    "description": "Narrows a commune name to a region (`RM`, `R8`…) when the name exists in two."
                  },
                  "sender": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "phone": {
                        "type": "string",
                        "minLength": 8,
                        "maxLength": 20
                      },
                      "email": {
                        "type": "string",
                        "maxLength": 320,
                        "format": "email"
                      }
                    },
                    "required": [
                      "name",
                      "phone",
                      "email"
                    ],
                    "additionalProperties": false,
                    "description": "Printed on the label as the sender."
                  },
                  "return_address": {
                    "type": "object",
                    "properties": {
                      "street": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 100
                      },
                      "number": {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 10
                      },
                      "supplement": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 200
                      }
                    },
                    "required": [
                      "street",
                      "number"
                    ],
                    "additionalProperties": false,
                    "description": "Where Chilexpress returns an undeliverable parcel (in the origin commune)."
                  },
                  "package": {
                    "type": "object",
                    "properties": {
                      "weight_kg": {
                        "type": "number",
                        "exclusiveMinimum": 0,
                        "maximum": 100
                      },
                      "height_cm": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 300
                      },
                      "width_cm": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 300
                      },
                      "length_cm": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 300
                      }
                    },
                    "additionalProperties": false,
                    "description": "One unit in its shipping box (default an iPhone box in a mailer: 0.6 kg, 20×12×8 cm). N units stack: weight and height × N."
                  },
                  "services": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "description": "Chilexpress service type: 2 Prioritario, 3 Express (día hábil siguiente), 4 Extendido, 5 Extremos, 41/42 encomiendas grandes."
                    },
                    "minItems": 1,
                    "maxItems": 6,
                    "description": "Services offered to buyers (default `[3]`, Express). Quotes show only these."
                  },
                  "declared_content": {
                    "type": "string",
                    "enum": [
                      "celular",
                      "tecnologia",
                      "articulos_personales",
                      "educacion",
                      "vestuario",
                      "otros"
                    ],
                    "description": "What the parcel contains, as Chilexpress declares it on the label."
                  },
                  "seller_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^[0-9]{6,9}$",
                    "description": "The company RUT without dots or check digit, when Chilexpress asks for it on the account."
                  }
                },
                "required": [
                  "environment",
                  "origin_commune",
                  "sender",
                  "return_address"
                ],
                "additionalProperties": false
              },
              "example": {
                "environment": "test",
                "rating_key": "<Cotizador primary key>",
                "shipments_key": "<Envíos primary key>",
                "customer_card_number": "18578680",
                "origin_commune": "Providencia",
                "sender": {
                  "name": "Tienda Ejemplo",
                  "phone": "+56912345678",
                  "email": "ventas@tienda.example.cl"
                },
                "return_address": {
                  "street": "Av. Ejemplo",
                  "number": "123",
                  "supplement": "Oficina 5"
                },
                "declared_content": "celular"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChilexpressConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": true,
                    "environment": "test",
                    "origin_coverage_code": "PROV",
                    "origin_commune": "Providencia",
                    "origin_region_code": "RM",
                    "customer_card_number": "18578680",
                    "keys": {
                      "rating": "a1b2••••9f",
                      "shipments": "c3d4••••0e",
                      "coverage": null
                    },
                    "can_dispatch": true,
                    "sender": {
                      "name": "Tienda Ejemplo",
                      "phone": "+56912345678",
                      "email": "ventas@tienda.example.cl"
                    },
                    "return_address": {
                      "street": "Av. Ejemplo",
                      "number": "123",
                      "supplement": "Oficina 5"
                    },
                    "package": {
                      "weight_kg": 0.6,
                      "height_cm": 8,
                      "width_cm": 12,
                      "length_cm": 20
                    },
                    "services": [
                      3
                    ],
                    "declared_content": "celular",
                    "seller_rut": null,
                    "connected_at": "2026-10-29T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Disconnect the store’s Chilexpress account",
        "description": "Disables the connection (reversible: connecting again re-enables it). Quotes and dispatches answer 409 `shipping_not_connected` until then; shipments already created keep their tracking links. Requires `tenant:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "responses": {
          "200": {
            "description": "The connection, now `connected: false`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ChilexpressConnection"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "connected": false,
                    "environment": "test",
                    "origin_coverage_code": "PROV",
                    "origin_commune": "Providencia",
                    "origin_region_code": "RM",
                    "customer_card_number": "18578680",
                    "keys": {
                      "rating": "a1b2••••9f",
                      "shipments": "c3d4••••0e",
                      "coverage": null
                    },
                    "can_dispatch": true,
                    "sender": {
                      "name": "Tienda Ejemplo",
                      "phone": "+56912345678",
                      "email": "ventas@tienda.example.cl"
                    },
                    "return_address": {
                      "street": "Av. Ejemplo",
                      "number": "123",
                      "supplement": "Oficina 5"
                    },
                    "package": {
                      "weight_kg": 0.6,
                      "height_cm": 8,
                      "width_cm": 12,
                      "length_cm": 20
                    },
                    "services": [
                      3
                    ],
                    "declared_content": "celular",
                    "seller_rut": null,
                    "connected_at": "2026-10-29T15:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/shipping/estimates": {
      "post": {
        "summary": "Estimate a delivery to where a buyer said",
        "description": "Prices a Chilexpress delivery from the store’s connection (its origin, the services it offers, its default box times `quantity`) to a destination as a buyer wrote it: a commune («Ñuñoa», «Stgo centro»), an address («Av. Alemania 0450, Temuco») and/or a region. Stores nothing: there is no quote id, and a checkout still needs `POST /orders/checkout/shipping-quotes`. Never creates a shipment. Without an `item` (or one with no price) the declared value, which changes Chilexpress’s price, is the median price of the store’s active catalog × `quantity` (`declared.basis`). `status`: `quoted` (destination + options), `needs_commune` (`reason` missing / ambiguous / region_mismatch / not_found, with `candidates` to ask about; send the chosen `coverage_code` back), `not_covered` (no offered service reaches it), `unknown_item`. The same answer the AI agent’s `quote_shipping` tool gives. 409 `shipping_not_connected` without a courier. Requires `orders:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Orders"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "commune": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The commune as the buyer wrote it («Ñuñoa», «Stgo centro»). Accents and case are ignored."
                  },
                  "address": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "The address as the buyer wrote it («Av. Alemania 0450, Temuco»); the commune is found in it."
                  },
                  "region": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "The region as the buyer wrote it («Araucanía», «RM», «IX»); narrows the commune."
                  },
                  "coverage_code": {
                    "type": "string",
                    "maxLength": 6,
                    "description": "A `coverage_code` from an earlier answer’s `candidates`, once the buyer picked one."
                  },
                  "item": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Optional: a variant id or product id from the catalog, or a SKU. Its catalog price is the declared value; omitted = one standard box, nothing declared."
                  },
                  "quantity": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 10
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "address": "Av. Alemania 0450, Temuco"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The estimate, or what is missing to make one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ShippingEstimate"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "status": "quoted",
                    "carrier": "chilexpress",
                    "destination": {
                      "coverage_code": "TEMU",
                      "commune": "Temuco",
                      "region_code": "R9",
                      "region": "Araucania"
                    },
                    "options": [
                      {
                        "service_code": 3,
                        "service_name": "Express",
                        "price_clp": 6965,
                        "estimated_business_days": 2
                      }
                    ],
                    "item": null,
                    "declared": {
                      "value_clp": 580000,
                      "basis": "catalog_median"
                    },
                    "package": {
                      "weight_kg": 0.6,
                      "height_cm": 8,
                      "width_cm": 12,
                      "length_cm": 20
                    },
                    "units": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/store-setup": {
      "get": {
        "summary": "Get the store setup steps",
        "description": "The steps a store still has before its agent sells end to end, each computed from real data on every read: `store` «Conecta tu tienda» (a Shopify or WooCommerce store connected, or `sells_without_store`; `attention` when the store asks to be reconnected), `catalog` «Revisa tu catálogo» (at least one active product and the owner marked it reviewed), `mercadopago` «Conecta Mercado Pago» (present when native orders can be paid by link, i.e. `order_payment.mode` is not `transfer_only`) and `bank_account` «Agrega tu cuenta para transferencias» (present for `transfer_first` / `transfer_only`). `order_payment` is how a native order is paid (the `final` charging mode; change it with `PUT /payment-settings/charging`). `applies` is true for an ecommerce workspace, the one whose Home shows the steps; `complete` when every listed step is `done`. Requires `tenant:read`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Store Setup"
        ],
        "responses": {
          "200": {
            "description": "The store setup",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "applies": {
                          "type": "boolean",
                          "description": "The workspace is an ecommerce one: Home shows the steps."
                        },
                        "complete": {
                          "type": "boolean",
                          "description": "Every step is `done`."
                        },
                        "steps": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "store",
                                  "catalog",
                                  "mercadopago",
                                  "bank_account"
                                ],
                                "description": "`store` «Conecta tu tienda» · `catalog` «Revisa tu catálogo» · `mercadopago` «Conecta Mercado Pago» (only when native orders can be paid by link) · `bank_account` «Agrega tu cuenta para transferencias» (only when they are paid by transfer first or only)."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "todo",
                                  "done",
                                  "attention"
                                ],
                                "description": "`attention` = the step was done and broke (the store asks to be reconnected)."
                              }
                            },
                            "required": [
                              "key",
                              "status"
                            ]
                          }
                        },
                        "order_payment": {
                          "type": "object",
                          "properties": {
                            "mode": {
                              "type": "string",
                              "enum": [
                                "mp_first",
                                "transfer_first",
                                "mp_only",
                                "transfer_only"
                              ],
                              "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "tenant",
                                "default"
                              ]
                            },
                            "default_mode": {
                              "type": "string",
                              "enum": [
                                "mp_first",
                                "transfer_first",
                                "mp_only",
                                "transfer_only"
                              ],
                              "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                            }
                          },
                          "required": [
                            "mode",
                            "source",
                            "default_mode"
                          ],
                          "description": "How a native order (built from the Vitrina catalog) is paid: the workspace’s `final` charging mode. Written with `PUT /payment-settings/charging` (`{ final }`)."
                        },
                        "store": {
                          "type": "object",
                          "properties": {
                            "connections": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "provider": {
                                    "type": "string",
                                    "enum": [
                                      "shopify",
                                      "woocommerce"
                                    ]
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "not_connected",
                                      "pending",
                                      "connected",
                                      "needs_reconnect"
                                    ]
                                  },
                                  "account": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "The shop’s name (Shopify) or URL (WooCommerce)."
                                  }
                                },
                                "required": [
                                  "provider",
                                  "status",
                                  "account"
                                ]
                              }
                            },
                            "sells_without_store": {
                              "type": "boolean",
                              "description": "The owner said the business sells from the Vitrina catalog alone."
                            }
                          },
                          "required": [
                            "connections",
                            "sells_without_store"
                          ]
                        },
                        "catalog": {
                          "type": "object",
                          "properties": {
                            "active": {
                              "type": "integer"
                            },
                            "native": {
                              "type": "integer"
                            },
                            "synced": {
                              "type": "integer"
                            },
                            "without_price": {
                              "type": "integer"
                            },
                            "without_photo": {
                              "type": "integer"
                            },
                            "reviewed_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "active",
                            "native",
                            "synced",
                            "without_price",
                            "without_photo",
                            "reviewed_at"
                          ]
                        },
                        "mercadopago": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean",
                              "description": "This deployment can connect Mercado Pago at all."
                            },
                            "connected": {
                              "type": "boolean"
                            },
                            "account": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "configured",
                            "connected",
                            "account"
                          ]
                        },
                        "bank_accounts": {
                          "type": "object",
                          "properties": {
                            "active": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "active"
                          ]
                        }
                      },
                      "required": [
                        "applies",
                        "complete",
                        "steps",
                        "order_payment",
                        "store",
                        "catalog",
                        "mercadopago",
                        "bank_accounts"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "applies": true,
                    "complete": false,
                    "steps": [
                      {
                        "key": "store",
                        "status": "done"
                      },
                      {
                        "key": "catalog",
                        "status": "todo"
                      },
                      {
                        "key": "mercadopago",
                        "status": "todo"
                      }
                    ],
                    "order_payment": {
                      "mode": "mp_first",
                      "source": "tenant",
                      "default_mode": "mp_first"
                    },
                    "store": {
                      "connections": [
                        {
                          "provider": "shopify",
                          "status": "connected",
                          "account": "Tienda Ejemplo"
                        },
                        {
                          "provider": "woocommerce",
                          "status": "not_connected",
                          "account": null
                        }
                      ],
                      "sells_without_store": false
                    },
                    "catalog": {
                      "active": 42,
                      "native": 0,
                      "synced": 42,
                      "without_price": 1,
                      "without_photo": 3,
                      "reviewed_at": null
                    },
                    "mercadopago": {
                      "configured": true,
                      "connected": false,
                      "account": null
                    },
                    "bank_accounts": {
                      "active": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "patch": {
        "summary": "Mark the catalog reviewed, or that the store sells without one",
        "description": "The owner’s two marks no table can know: `catalog_reviewed` (`true` stamps the review, `false` clears it) and `sells_without_store` (no Shopify / WooCommerce to connect; the Vitrina catalog is the store). Answers with the recomputed setup. Requires `tenant:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Store Setup"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalog_reviewed": {
                    "type": "boolean",
                    "description": "`true` stamps the catalog as reviewed now (kept if already stamped); `false` clears it."
                  },
                  "sells_without_store": {
                    "type": "boolean",
                    "description": "`true` = no Shopify / WooCommerce to connect: the business sells from the Vitrina catalog, and «Conecta tu tienda» is done."
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "catalog_reviewed": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The store setup after the change",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "applies": {
                          "type": "boolean",
                          "description": "The workspace is an ecommerce one: Home shows the steps."
                        },
                        "complete": {
                          "type": "boolean",
                          "description": "Every step is `done`."
                        },
                        "steps": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "key": {
                                "type": "string",
                                "enum": [
                                  "store",
                                  "catalog",
                                  "mercadopago",
                                  "bank_account"
                                ],
                                "description": "`store` «Conecta tu tienda» · `catalog` «Revisa tu catálogo» · `mercadopago` «Conecta Mercado Pago» (only when native orders can be paid by link) · `bank_account` «Agrega tu cuenta para transferencias» (only when they are paid by transfer first or only)."
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "todo",
                                  "done",
                                  "attention"
                                ],
                                "description": "`attention` = the step was done and broke (the store asks to be reconnected)."
                              }
                            },
                            "required": [
                              "key",
                              "status"
                            ]
                          }
                        },
                        "order_payment": {
                          "type": "object",
                          "properties": {
                            "mode": {
                              "type": "string",
                              "enum": [
                                "mp_first",
                                "transfer_first",
                                "mp_only",
                                "transfer_only"
                              ],
                              "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                            },
                            "source": {
                              "type": "string",
                              "enum": [
                                "tenant",
                                "default"
                              ]
                            },
                            "default_mode": {
                              "type": "string",
                              "enum": [
                                "mp_first",
                                "transfer_first",
                                "mp_only",
                                "transfer_only"
                              ],
                              "description": "`mp_first` = Mercado Pago link first, transfer as the alternative; `transfer_first` = transfer first, link only if the customer asks; `mp_only` / `transfer_only` = only that method. A mode that needs Mercado Pago without a connection falls back to the transfer; an `*_only` mode with nothing to offer tells the customer the team will send the details."
                            }
                          },
                          "required": [
                            "mode",
                            "source",
                            "default_mode"
                          ],
                          "description": "How a native order (built from the Vitrina catalog) is paid: the workspace’s `final` charging mode. Written with `PUT /payment-settings/charging` (`{ final }`)."
                        },
                        "store": {
                          "type": "object",
                          "properties": {
                            "connections": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "provider": {
                                    "type": "string",
                                    "enum": [
                                      "shopify",
                                      "woocommerce"
                                    ]
                                  },
                                  "status": {
                                    "type": "string",
                                    "enum": [
                                      "not_connected",
                                      "pending",
                                      "connected",
                                      "needs_reconnect"
                                    ]
                                  },
                                  "account": {
                                    "type": [
                                      "string",
                                      "null"
                                    ],
                                    "description": "The shop’s name (Shopify) or URL (WooCommerce)."
                                  }
                                },
                                "required": [
                                  "provider",
                                  "status",
                                  "account"
                                ]
                              }
                            },
                            "sells_without_store": {
                              "type": "boolean",
                              "description": "The owner said the business sells from the Vitrina catalog alone."
                            }
                          },
                          "required": [
                            "connections",
                            "sells_without_store"
                          ]
                        },
                        "catalog": {
                          "type": "object",
                          "properties": {
                            "active": {
                              "type": "integer"
                            },
                            "native": {
                              "type": "integer"
                            },
                            "synced": {
                              "type": "integer"
                            },
                            "without_price": {
                              "type": "integer"
                            },
                            "without_photo": {
                              "type": "integer"
                            },
                            "reviewed_at": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "active",
                            "native",
                            "synced",
                            "without_price",
                            "without_photo",
                            "reviewed_at"
                          ]
                        },
                        "mercadopago": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean",
                              "description": "This deployment can connect Mercado Pago at all."
                            },
                            "connected": {
                              "type": "boolean"
                            },
                            "account": {
                              "type": [
                                "string",
                                "null"
                              ]
                            }
                          },
                          "required": [
                            "configured",
                            "connected",
                            "account"
                          ]
                        },
                        "bank_accounts": {
                          "type": "object",
                          "properties": {
                            "active": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "active"
                          ]
                        }
                      },
                      "required": [
                        "applies",
                        "complete",
                        "steps",
                        "order_payment",
                        "store",
                        "catalog",
                        "mercadopago",
                        "bank_accounts"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "applies": true,
                    "complete": false,
                    "steps": [
                      {
                        "key": "store",
                        "status": "done"
                      },
                      {
                        "key": "catalog",
                        "status": "todo"
                      },
                      {
                        "key": "mercadopago",
                        "status": "todo"
                      }
                    ],
                    "order_payment": {
                      "mode": "mp_first",
                      "source": "tenant",
                      "default_mode": "mp_first"
                    },
                    "store": {
                      "connections": [
                        {
                          "provider": "shopify",
                          "status": "connected",
                          "account": "Tienda Ejemplo"
                        },
                        {
                          "provider": "woocommerce",
                          "status": "not_connected",
                          "account": null
                        }
                      ],
                      "sells_without_store": false
                    },
                    "catalog": {
                      "active": 42,
                      "native": 0,
                      "synced": 42,
                      "without_price": 1,
                      "without_photo": 3,
                      "reviewed_at": null
                    },
                    "mercadopago": {
                      "configured": true,
                      "connected": false,
                      "account": null
                    },
                    "bank_accounts": {
                      "active": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals": {
      "get": {
        "summary": "List proactive messages waiting on a person",
        "description": "Every composed outbound message this workspace is holding for a human decision, oldest OBLIGATION first — ordered by `due_at` rather than by when the draft was written, because what is running out of time is the obligation, not the proposal. Each entry carries the draft exactly as it would be sent, the model's operator-facing `reason` (never shown to a customer), who was asked, and `expired: true` once the obligation passed its lateness bound — those stay in the list on purpose, so a reviewer sees that one ran out of time rather than finding it silently gone. `expiresAt` is that bound: `dueAt` plus the action’s maximum lateness, as an ISO timestamp, or null when the action has no limit and never expires. Filter by `rail` for one rail's queue, or by `assignee_user_id` for one person's. An entry with a null `assignee_user_id` was routed to the workspace admins, which is a real answer and not a missing one. Rail `api` holds the sends a CONNECTED APP made past its fan-out limit (ADR 0106 §3.2); those entries carry `via: { kind, name }` — the app that wrote them («vía Claude») — and `via` is null on every other rail. `whyNow` is the owner-facing reason, in the product's register (null when the composer wrote none; `reason` stays audit material). Rail `recontacto` entries carry `recontacto: { attempt_id, step_label, auto_send_at, auto_send_at_label, template, window_closes_at, window_note }`: the step as «Si no responde» names it and, for a handed-off conversation's draft, when it sends itself if nobody reviews it («mañana 18:00», the conversation strip's own words), and whether the draft rides an approved WhatsApp template; for a free-text WhatsApp or Instagram draft, when its 24 h window closes (`window_closes_at`, tenant clock) and that in the owner’s words (`window_note`: «Envíalo antes de mañana 14:05; después sale como plantilla aprobada»). Approving a WhatsApp draft past its window re-composes it as an approved template (outcome `deferred` when it must wait for one); an Instagram one past its window is dropped (outcome `stale`, G53). `due_at` is not that moment. Null on every other rail.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "service_lifecycle",
                "recontacto",
                "seguimiento",
                "campaign",
                "first_touch",
                "api",
                "budget_followup",
                "sequence"
              ]
            },
            "required": false,
            "name": "rail",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "assignee_user_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending approvals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "items": [
                      {
                        "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                        "rail": "recontacto",
                        "purpose": "recontacto_stalled",
                        "obligationClass": "follow_up",
                        "contactId": "6d2e3f4a-5b6c-4d7e-8f90-1a2b3c4d5e6f",
                        "subjectType": "lead",
                        "subjectId": "7e3f4a5b-6c7d-4e8f-901a-2b3c4d5e6f70",
                        "conversationId": "8f4a5b6c-7d8e-4f90-a12b-3c4d5e6f7081",
                        "dueAt": "2026-11-03T13:00:00.000Z",
                        "requestedAt": "2026-11-03T12:05:00.000Z",
                        "assigneeUserId": "9a5b6c7d-8e9f-4a01-b23c-4d5e6f708192",
                        "message": "Hola Camila, te escribo por el iPhone 15 Pro que viste. Te sigue interesando?",
                        "subject": null,
                        "reason": "Asked about the 15 Pro two days ago and did not answer the price.",
                        "whyNow": "Preguntó por el iPhone 15 Pro hace dos días y no respondió.",
                        "recontacto": {
                          "attempt_id": "ab6c7d8e-9f0a-4b12-834d-5e6f708192a3",
                          "step_label": "Conversó y dejó de responder · paso 3 de 4",
                          "auto_send_at": null,
                          "auto_send_at_label": null,
                          "template": true,
                          "handoff": null,
                          "window_closes_at": null,
                          "window_note": null
                        },
                        "channel": "whatsapp",
                        "via": null,
                        "decisionVersion": 1,
                        "expired": false,
                        "expiresAt": "2026-11-10T13:00:00.000Z"
                      }
                    ],
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}": {
      "get": {
        "summary": "Read one pending approval",
        "description": "One queue entry by `outbound_action` id. 404 both when it does not exist and when it belongs to another workspace — the difference is exactly what an existence oracle would leak.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The queue entry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "rail": "recontacto",
                    "purpose": "recontacto_stalled",
                    "obligationClass": "follow_up",
                    "contactId": "6d2e3f4a-5b6c-4d7e-8f90-1a2b3c4d5e6f",
                    "subjectType": "lead",
                    "subjectId": "7e3f4a5b-6c7d-4e8f-901a-2b3c4d5e6f70",
                    "conversationId": "8f4a5b6c-7d8e-4f90-a12b-3c4d5e6f7081",
                    "dueAt": "2026-11-03T13:00:00.000Z",
                    "requestedAt": "2026-11-03T12:05:00.000Z",
                    "assigneeUserId": "9a5b6c7d-8e9f-4a01-b23c-4d5e6f708192",
                    "message": "Hola Camila, te escribo por el iPhone 15 Pro que viste. Te sigue interesando?",
                    "subject": null,
                    "reason": "Asked about the 15 Pro two days ago and did not answer the price.",
                    "whyNow": "Preguntó por el iPhone 15 Pro hace dos días y no respondió.",
                    "recontacto": {
                      "attempt_id": "ab6c7d8e-9f0a-4b12-834d-5e6f708192a3",
                      "step_label": "Conversó y dejó de responder · paso 3 de 4",
                      "auto_send_at": null,
                      "auto_send_at_label": null,
                      "template": true,
                      "handoff": null,
                      "window_closes_at": null,
                      "window_note": null
                    },
                    "channel": "whatsapp",
                    "via": null,
                    "decisionVersion": 1,
                    "expired": false,
                    "expiresAt": "2026-11-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}/approve": {
      "post": {
        "summary": "Approve (and optionally edit) a pending message",
        "description": "Records the approval; the message is RELEASED 5 minutes later unless undone (`POST /outbound/approvals/{id}/undo`), and the response is `outcome: \"scheduled\"` with `releaseAt` (the undo deadline) and `releaseKind: \"approve\"`. The approved entry leaves the pending list and count immediately. At release it goes — through the shared executor, which re-runs the full policy kernel in enforce mode and re-checks freshness immediately before the provider. Approving is therefore a decision to send, not an instruction to bypass anything. When released, the outcome (visible afterwards on the action) is: `sent`; `stale` (the customer wrote while the proposal waited — nothing was sent and the obligation went back for a fresh evaluation); `deferred` (quiet hours, a lost arbitration, a transport backoff — it will come back on its own); or `blocked` (the kernel or the provider refused, and `reason` says which). An optional `message` replaces the draft text and NOTHING else: not the purpose, the legal basis, the channel or the obligation class, which came from the protocol and are not editable through a review screen. Recontacto proposals refuse edits outright — that rail's drafts can be approved WhatsApp templates, whose body belongs to Meta. So do `api` drafts: a member approves what the connected app wrote, or refuses it. Approving one waives its Advertencias (the approving member is the acknowledgment) and still stops on any Bloqueo. Requires `outbound_approvals:write`, `campaigns:write` AND `messages:send`. 409 when the entry is no longer pending, or when the obligation passed its lateness bound.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "subject": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 78
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "message": null,
                "note": "Revisado"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What happened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "scheduled",
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "reason": null,
                    "runId": null,
                    "releaseAt": "2026-11-03T12:10:00.000Z",
                    "releaseKind": "approve"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}/reject": {
      "post": {
        "summary": "Refuse a pending message",
        "description": "Cancels the action, with the reviewer and the reason recorded on it. The action is CANCELLED rather than left in the queue on purpose: a refused proposal that stays pending is a proposal the next drain can execute. It does not stop the run — later steps of the same protocol are separate obligations and are unaffected (use the service-lifecycle stop or pause verbs for that). Requires only `outbound_approvals:write`: refusing to send is never the dangerous direction. Rail `recontacto`: `reason_code` `wrong_moment` / `wrong_wording` (or free text) drops this message only and the follow-up continues (G53 `draft_rejected`); `already_handled` / `should_not_contact` — or a `reason` that says so, such as «A esta persona no hay que escribirle» — also stops the follow-up (G33 `operator_dismissed`), as `/dismiss` does. The response says which in `stopped`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "reason_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "wrong_moment",
                      "wrong_wording",
                      "already_handled",
                      "should_not_contact"
                    ]
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason_code": "wrong_moment",
                "reason": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The rejection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "rejected",
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "reason": "wrong_moment",
                    "stopped": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}/dismiss": {
      "post": {
        "summary": "Refuse a pending message and stop the follow-up",
        "description": "«Descartar seguimiento». Cancels the action exactly as `/reject` does, and tells the rail to stop the follow-up too. Rail `recontacto`: the person's ladder halts (G33 `operator_dismissed`): no later step of this path is drafted or sent; a new message from the customer starts a new episode. Rails with no notion of stopping treat it as a refusal. The response is the rejection, with `stopped: true` when the follow-up stopped. Requires only `outbound_approvals:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason": "Ya compró en la tienda"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The dismissal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "rejected",
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "reason": "Ya compró en la tienda",
                    "stopped": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/scheduled": {
      "get": {
        "summary": "Decisions inside their 5-minute undo window",
        "description": "Approvals and discards recorded in the last 5 minutes and not yet released, soonest `releaseAt` first. Each can be undone until `releaseAt`. Response: `{ graceSeconds, items: [{ actionId, rail, kind: \"approve\" | \"discard\", releaseAt, contactId, message }] }`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled decisions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "graceSeconds": 300,
                    "items": [
                      {
                        "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                        "rail": "recontacto",
                        "kind": "approve",
                        "releaseAt": "2026-11-03T12:10:00.000Z",
                        "contactId": "6d2e3f4a-5b6c-4d7e-8f90-1a2b3c4d5e6f",
                        "message": "Hola Camila, te escribo por el iPhone 15 Pro que viste. Te sigue interesando?"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}/discard": {
      "post": {
        "summary": "Discard a pending message, undoable for 5 minutes",
        "description": "Like `/reject` (or `/dismiss` with `stop: true`) but the message leaves the pending list at once and the cancellation is applied after the 5-minute window, so `POST /outbound/approvals/{id}/undo` brings it back. Response `outcome: \"scheduled\"`, `releaseKind: \"discard\"`, `releaseAt`. Requires `outbound_approvals:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "reason_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "wrong_moment",
                      "wrong_wording",
                      "already_handled",
                      "should_not_contact"
                    ]
                  },
                  "stop": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "reason_code": "wrong_moment",
                "reason": null
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The scheduled discard",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "scheduled",
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "reason": null,
                    "runId": null,
                    "releaseAt": "2026-11-03T12:10:00.000Z",
                    "releaseKind": "discard"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/{id}/undo": {
      "post": {
        "summary": "Undo an approval or discard inside its window",
        "description": "Returns a scheduled approval or discard to the pending list. 409 once the 5-minute window closed (the release was claimed) or when nothing was scheduled. Exactly one of undo and release wins, never both. Requires `outbound_approvals:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Back in the queue",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "outcome": "deferred",
                    "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                    "reason": "undone",
                    "runId": null,
                    "undone": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/outbound/approvals/batch/approve": {
      "post": {
        "summary": "Approve many pending messages with one undo window",
        "description": "Body: exactly one of `ids` (selected, max 500) or `all: true` (every message the approval queue shows, the same predicate as the pending count, narrowed optionally by `rail` and `purpose` = the step; capped at 500 per call, `truncated` says more remain). Each item is released 5 minutes after the call unless undone, and goes through the same gates as a single approval (contact hours, pauses, policy). Response: `{ requested, scheduled, undone, skipped, truncated, items: [{ actionId, status: \"scheduled\" | \"undone\" | \"skipped\", kind, releaseAt, reason }] }` - one outcome per item; a skipped item (decided elsewhere, expired, unknown) never fails the rest. Requires `outbound_approvals:write` AND `campaigns:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  },
                  "all": {
                    "type": "boolean",
                    "enum": [
                      true
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "service_lifecycle",
                      "recontacto",
                      "seguimiento",
                      "campaign",
                      "first_touch",
                      "api",
                      "budget_followup",
                      "sequence"
                    ]
                  },
                  "purpose": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "all": true,
                "rail": "recontacto",
                "purpose": "recontacto_stalled"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "requested": 1,
                    "scheduled": 1,
                    "undone": 0,
                    "skipped": 0,
                    "truncated": false,
                    "items": [
                      {
                        "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                        "status": "scheduled",
                        "kind": "approve",
                        "releaseAt": "2026-11-03T12:10:00.000Z",
                        "reason": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/outbound/approvals/batch/discard": {
      "post": {
        "summary": "Discard many pending messages (undoable)",
        "description": "Same selector as batch approve. Items leave the pending list at once; the cancellation applies after the 5-minute window unless undone. Response: `{ requested, scheduled, undone, skipped, truncated, items: [{ actionId, status: \"scheduled\" | \"undone\" | \"skipped\", kind, releaseAt, reason }] }` - one outcome per item; a skipped item (decided elsewhere, expired, unknown) never fails the rest. Requires `outbound_approvals:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  },
                  "all": {
                    "type": "boolean",
                    "enum": [
                      true
                    ]
                  },
                  "rail": {
                    "type": "string",
                    "enum": [
                      "service_lifecycle",
                      "recontacto",
                      "seguimiento",
                      "campaign",
                      "first_touch",
                      "api",
                      "budget_followup",
                      "sequence"
                    ]
                  },
                  "purpose": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "reason_code": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "wrong_moment",
                      "wrong_wording",
                      "already_handled",
                      "should_not_contact"
                    ]
                  },
                  "stop": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "ids": [
                  "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e"
                ],
                "reason_code": "wrong_moment"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "requested": 1,
                    "scheduled": 1,
                    "undone": 0,
                    "skipped": 0,
                    "truncated": false,
                    "items": [
                      {
                        "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                        "status": "scheduled",
                        "kind": "approve",
                        "releaseAt": "2026-11-03T12:10:00.000Z",
                        "reason": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/outbound/approvals/batch/undo": {
      "post": {
        "summary": "Undo many scheduled decisions",
        "description": "Undo approvals/discards inside their window. Response: `{ requested, scheduled, undone, skipped, truncated, items: [{ actionId, status: \"scheduled\" | \"undone\" | \"skipped\", kind, releaseAt, reason }] }` - one outcome per item; a skipped item (decided elsewhere, expired, unknown) never fails the rest. Requires `outbound_approvals:write`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Outbound approvals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "ids"
                ],
                "additionalProperties": false
              },
              "example": {
                "ids": [
                  "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "requested": 1,
                    "scheduled": 0,
                    "undone": 1,
                    "skipped": 0,
                    "truncated": false,
                    "items": [
                      {
                        "actionId": "5c1d2e3f-4a5b-4c6d-8e7f-901a2b3c4d5e",
                        "status": "undone",
                        "kind": null,
                        "releaseAt": null,
                        "reason": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/billing/invoices/{id}/transfer-claim/comprobante": {
      "post": {
        "summary": "Attach or replace the comprobante of a pending «Ya pagué»",
        "description": "Adds the proof of the transfer to the cobro’s pending «Ya pagué por transferencia» claim, or replaces the one already there (the old file is deleted). A PDF, JPG or PNG of at most 10 MB; the type is read from the bytes, never from the request (415 `comprobante_type`; 413 `comprobante_too_large`; 400 `comprobante_empty` / `comprobante_missing`). It is a claim, not proof: it changes nothing on the cobro, its collection or its automatic charge; the Vitrina team checks the transfer. 409 `no_pending_claim` when the cobro has no pending claim (claim first, or attach the file to the claim itself); 409 `comprobante_frozen` when the claim’s file already went with a recorded payment; 404 for a cobro of another workspace. Requires `billing:write` (owners).\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary part)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "comprobante.pdf",
                "content": "JVBERi0xLjQK…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The file now on the pending claim",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClaimComprobanteUpload"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "comprobante": {
                      "id": "5b0e6c1a-9a7d-4c3e-8f21-0c4d2e1f7a90",
                      "invoice_id": "9a1bd325-3ab8-47e8-afe1-7b87c550b139",
                      "claimed_at": "2026-11-05T13:31:00.000Z",
                      "filename": "comprobante_transferencia.pdf",
                      "mime": "application/pdf",
                      "size_bytes": 48211,
                      "uploaded_at": "2026-11-05T13:31:02.000Z",
                      "state": "pending",
                      "rejected_at": null,
                      "received_payment_id": null
                    },
                    "replaced": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/billing/invoices/{id}/transfer-claim/comprobantes": {
      "get": {
        "summary": "The comprobantes attached to a cobro’s «Ya pagué» claims",
        "description": "Every file the workspace attached to a «Ya pagué» on this cobro, the newest claim first, with what became of each claim (`state`). A rejected claim’s file stays here as history. Descriptors only; download one with `GET …/comprobantes/{comprobanteId}`. 404 for a cobro of another workspace. Requires `billing:read` (owners and admins).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Billing"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The claim files",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/ClaimComprobante"
                          },
                          {
                            "type": "object"
                          }
                        ]
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "5b0e6c1a-9a7d-4c3e-8f21-0c4d2e1f7a90",
                      "invoice_id": "9a1bd325-3ab8-47e8-afe1-7b87c550b139",
                      "claimed_at": "2026-11-05T13:31:00.000Z",
                      "filename": "comprobante_transferencia.pdf",
                      "mime": "application/pdf",
                      "size_bytes": 48211,
                      "uploaded_at": "2026-11-05T13:31:02.000Z",
                      "state": "pending",
                      "rejected_at": null,
                      "received_payment_id": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sandbox/clinic": {
      "post": {
        "summary": "Provision a synthetic demonstration clinic",
        "description": "Creates a workspace holding a generated dental clinic — professionals, catálogo, a year of agenda, presupuestos with their lineages, payments in instalments, expenses, boletas, packs and commissions — and mints an MCP key for it. The clinic is a pure function of `seed`, so the same seed reproduces the same clinic. Zero real data: RUTs come from a reserved, never-issued block, phones from an unassigned mobile block and e-mail addresses from RFC 2606 domains. Every outbound send the workspace makes is captured instead of delivered, and no real channel may be connected to it. Idempotent per asking workspace: a second call returns the first call's sandbox with `created: false` and no key.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "seed": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 64
                  },
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "months": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 24
                  },
                  "patient_count": {
                    "type": "integer",
                    "minimum": 20,
                    "maximum": 2000
                  },
                  "professional_count": {
                    "type": "integer",
                    "minimum": 4,
                    "maximum": 6
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "seed": "docs-2697-clinic"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The sandbox this workspace had already provisioned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "created": false,
                    "tenant": {
                      "id": "cdc3ebc7-ca87-4dec-aadc-583c6af4fdc2",
                      "name": "Clínica Dental Aurora (demo)",
                      "slug": "clinica-demo-2"
                    },
                    "seed": "docs-2697-clinic",
                    "stats": {},
                    "mcp": null
                  }
                }
              }
            }
          },
          "201": {
            "description": "The sandbox, its seed, its row counts and its MCP key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "created": true,
                    "tenant": {
                      "id": "cdc3ebc7-ca87-4dec-aadc-583c6af4fdc2",
                      "name": "Clínica Dental Aurora (demo)",
                      "slug": "clinica-demo-2"
                    },
                    "seed": "docs-2697-clinic",
                    "stats": {
                      "professionals": 5,
                      "patients": 600,
                      "appointments": 4609,
                      "budgets": 255,
                      "payments": 591
                    },
                    "mcp": {
                      "secret": "sk_test_virs…",
                      "prefix": "sk_test_virs",
                      "scopes": [
                        "clinic:read",
                        "clinic:write",
                        "clinic_money:read",
                        "clinic_money:write",
                        "clinic_admin:write",
                        "clinic_insights:read",
                        "conversations:read",
                        "conversations:write",
                        "contacts:read",
                        "appointments:read",
                        "appointments:write",
                        "sandbox:read"
                      ],
                      "url": "/mcp"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sandbox/automotive": {
      "post": {
        "summary": "Provision a synthetic automotora (the sandbox for sk_test_ keys)",
        "description": "Creates this workspace's SANDBOX: a separate workspace bound to it, holding a generated dealership — ~40 units with real makes, models and CLP prices, 30 prospects each with a conversation and a lead, 10 cotizaciones and 5 reservas — and its whole back office, issued through the same services a dealer uses: legal identity, a team with commission schemes, notas de compra, consignaciones, cost lines, ~11 approved notas de venta over six months (with payments, credit, retomas and a settled consignment), post-sale obligations, compraventas, a pending price approval and upcoming test drives. Steps a service refused are listed in `failures` (empty when healthy). It mints one `sk_test_` key for it, returned once. Test keys minted later with `POST /api-keys {livemode:false}` bind to the same sandbox. A test key authenticates ONLY there: it can never read or write this workspace. Zero real data (phones in an unassigned mobile block, e-mail on RFC 2606 domains, RUTs in a never-issued block, plates in the not-yet-issued ZZ series, VINs in the unassigned 0 region). Every outbound send is captured instead of delivered, and no real channel may be connected. Deterministic from `seed`. Idempotent: one sandbox per workspace, of either vertical — a second call returns it with `created: false`, its `vertical` and no key.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "seed": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 64
                  },
                  "name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 120
                  },
                  "legal_name": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 200
                  },
                  "vehicle_count": {
                    "type": "integer",
                    "minimum": 10,
                    "maximum": 200
                  },
                  "contact_count": {
                    "type": "integer",
                    "minimum": 15,
                    "maximum": 300
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "seed": "docs-2697-demo",
                "name": "Autos del Pacífico"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The sandbox this workspace had already provisioned",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "created": false,
                    "tenant": {
                      "id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                      "name": "Autos Alameda (demostración)",
                      "slug": "autos-alameda-demo-3"
                    },
                    "vertical": "automotive",
                    "seed": "docs-2697-demo",
                    "stats": {},
                    "test_key": null
                  }
                }
              }
            }
          },
          "201": {
            "description": "The sandbox, its seed, its row counts and its sk_test_ key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "created": true,
                    "tenant": {
                      "id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                      "name": "Autos del Pacífico (demostración)",
                      "slug": "autos-del-pacifico-demo-3"
                    },
                    "vertical": "automotive",
                    "seed": "docs-2697-demo",
                    "stats": {
                      "vehicles": 40,
                      "contacts": 92,
                      "leads": 54,
                      "quotes": 10,
                      "reservations": 5,
                      "team_member": 5,
                      "purchase_note": 33,
                      "consignment_contract": 5,
                      "vehicle_cost_entry": 85,
                      "sale_note": 11,
                      "liquidacion": 1,
                      "compraventa": 2,
                      "test_drive": 4
                    },
                    "failures": [],
                    "test_key": {
                      "secret": "sk_test_gZCK…",
                      "prefix": "sk_test_gZCK",
                      "scopes": [
                        "marketplace:read",
                        "marketplace:write",
                        "leads:read",
                        "leads:write",
                        "contacts:read",
                        "contacts:write",
                        "conversations:read",
                        "conversations:write",
                        "messages:send",
                        "quotes:read",
                        "quotes:write",
                        "reservations:read",
                        "reservations:write",
                        "sandbox:read"
                      ],
                      "livemode": false
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sandbox/reset": {
      "post": {
        "summary": "Reset a sandbox to the data its seed generates",
        "description": "Deletes the sandbox's synthetic data — and everything created on top of it since (leads, notes, cotizaciones, citas, …) — plus every captured send, then regenerates the dataset from the sandbox's `seed` with the generator for its vertical (`automotive` or `healthcare`). Kept: the workspace, its members, its API keys (test keys keep working), its webhook subscriptions and its free subscription. Same seed, same data: the same units, prices, buyers or patients, with dates re-anchored to the day of the reset and display ids starting over. Called from a live workspace it resets THAT workspace's sandbox (409 `SANDBOX_NOT_PROVISIONED` when it has none); called from inside a sandbox (an `sk_test_` key) it resets that sandbox. It can never reach a live workspace.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "The sandbox that was reset: `tenant_id`, `vertical`, `seed`, the rows regenerated per table (`counts`, back office included), the rows removed (`deleted`) and any back-office step a service refused (`failures`, empty when healthy)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                    "vertical": "automotive",
                    "seed": "docs-2697-demo",
                    "counts": {
                      "vehicle": 40,
                      "contact": 92,
                      "lead": 54,
                      "sale_note": 11,
                      "purchase_note": 33
                    },
                    "deleted": {
                      "vehicle": 40,
                      "contact": 92,
                      "sale_note": 11,
                      "sandbox_outbound_capture": 1
                    },
                    "failures": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sandbox": {
      "get": {
        "summary": "This workspace's sandbox state",
        "description": "Whether the calling workspace IS a sandbox (and from which seed), and whether it has provisioned one. Both halves are about the caller; there is no cross-tenant read here.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "responses": {
          "200": {
            "description": "Sandbox state of the calling workspace",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "is_sandbox": false,
                    "seed": null,
                    "sandbox_of": null,
                    "vertical": null,
                    "provisioned_sandbox": {
                      "tenant_id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                      "name": "Autos Alameda (demostración)",
                      "slug": "autos-alameda-demo-3",
                      "seed": "docs-2697-demo",
                      "vertical": "automotive",
                      "created_at": "2026-09-23T05:10:02.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sandbox/outbound": {
      "get": {
        "summary": "Outbound sends that were captured instead of delivered",
        "description": "The demonstration surface: every WhatsApp, e-mail, voice call, webhook and document a sandbox workspace tried to send, with the rendered body and the destination the rail had computed. Always empty on a workspace that is not a sandbox — the rail exists everywhere and only ever fires inside one.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "whatsapp",
                "email",
                "instagram",
                "messenger",
                "webchat",
                "voice",
                "webhook",
                "tax_document",
                "portal",
                "other"
              ]
            },
            "required": false,
            "name": "rail",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            },
            "required": false,
            "name": "offset",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Captured sends, newest first, with a per-rail summary",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0aab045e-1441-41f3-9258-8a780c501b1a",
                      "tenant_id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                      "rail": "whatsapp",
                      "provider": "whatsapp_cloud",
                      "destination": "56908450000",
                      "subject": null,
                      "body": "Hola, te confirmo tu cita para el jueves a las 10.",
                      "conversation_id": "f6506b7f-a342-48d6-8590-011f263f6b13",
                      "origin": "provider.sendTextMessage",
                      "idempotency_key": "74ec9181-edf1-48dc-aab3-4883c7eb2e2e",
                      "captured_at": "2026-09-23T05:28:53.464Z"
                    }
                  ],
                  "meta": {
                    "summary": [
                      {
                        "rail": "whatsapp",
                        "count": 1
                      }
                    ],
                    "rails": [
                      "whatsapp",
                      "email",
                      "instagram",
                      "messenger",
                      "webchat",
                      "voice",
                      "tax_document",
                      "portal",
                      "other"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sandbox/outbound/{id}": {
      "get": {
        "summary": "One outbound send that was captured instead of delivered",
        "description": "A single row of the capture ledger — the rendered body, the destination the rail computed, the raw payload the intercepted call was given, the call site (`origin`) and, when the send carried one, its `idempotency_key` (a retried send with the same key returns this same row). Portal publishing (Chileautos, Mercado Libre, Facebook Marketplace listings) captures on rail `portal`. Scoped to the calling workspace: another workspace's id answers 404, never 403.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sandbox"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The captured send",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "tenant_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "rail": {
                          "type": "string"
                        },
                        "provider": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "destination": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "subject": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "body": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "payload": {},
                        "conversation_id": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "uuid"
                        },
                        "origin": {
                          "type": "string"
                        },
                        "idempotency_key": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "captured_at": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "id",
                        "tenant_id",
                        "rail",
                        "provider",
                        "destination",
                        "subject",
                        "body",
                        "conversation_id",
                        "origin",
                        "idempotency_key",
                        "captured_at"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0aab045e-1441-41f3-9258-8a780c501b1a",
                    "tenant_id": "34d213e0-5cac-4c86-a57c-4db28d59b9ec",
                    "rail": "whatsapp",
                    "provider": "whatsapp_cloud",
                    "destination": "56908450000",
                    "subject": null,
                    "body": "Hola, te confirmo tu cita para el jueves a las 10.",
                    "payload": {
                      "to": "56908450000",
                      "type": "text",
                      "text": {
                        "body": "Hola, te confirmo tu cita para el jueves a las 10."
                      }
                    },
                    "conversation_id": "f6506b7f-a342-48d6-8590-011f263f6b13",
                    "origin": "provider.sendTextMessage",
                    "idempotency_key": "74ec9181-edf1-48dc-aab3-4883c7eb2e2e",
                    "captured_at": "2026-09-23T05:28:53.464Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/roles": {
      "get": {
        "summary": "List the workspace’s custom roles",
        "description": "Newest first, with a `total`. Unpaginated — a workspace has a handful of custom roles. Built-in tiers are not returned: they are not rows on this table. On a clinic workspace the list always includes the packaged seats (`preset_key` `clinic_reception` | `clinic_professional`; the third, Administración, is the built-in `admin` tier), created on first list; their permissions are managed by Vitrina (editing them, or deleting the role, is a **409**).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Roles"
        ],
        "responses": {
          "200": {
            "description": "The workspace’s custom roles",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d2d2d2d2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Vendedor sucursal",
                      "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
                      "scopes": [
                        "conversations:read",
                        "conversations:write",
                        "leads:read",
                        "leads:write"
                      ],
                      "record_visibility": "assigned",
                      "stock_visibility": "own_locations",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a custom role",
        "description": "`scopes` is optional; omitting it creates an EMPTY role, which grants nothing and is accepted. Only known, non-owner-only permissions are allowed — the wildcard `*`, an unknown string and the owner-only scopes (`billing:write`, `tenant:delete`) are all a **400**.\n\n`record_visibility` is `all` | `assigned_unassigned` | `assigned` and narrows conversations, tickets and leads by assignee. `stock_visibility` is `all` | `own_locations` and narrows VEHICLE reads to the sucursales on the member’s own `workspace_member.location_ids` — a member on an `own_locations` role with no sucursales sees no stock at all, and a vehicle with no `location_id` is invisible to such a member by definition. `project_visibility` (Módulo Proyectos) is `all` | `sales_office` | `assigned` and narrows LEADS (and the proyectos list) to the proyectos the member works: `assigned` = the proyectos they are assigned to (ejecutivo de ventas), `sales_office` = those plus every proyecto served by a sala de ventas on their `location_ids` (jefe de sala). A lead the member owns stays visible. All three default to `all`. `max_discount_percent` (Módulo Proyectos) is the role’s límite de descuento: the largest effective discount, in percent over the list total, a member on the role may send on a cotización without approval. `null` (the default) is no limit; above the limit a member holding `cotizacion_discount:approve` must approve first. `clinic_record_visibility` (clinics) is `all` | `own_patients`: with `own_patients` a `clinic_record:read` holder opens only the clinical records of patients their linked professional attended or is assigned to (lifted by the clinic setting `clinic_records_shared_across_professionals`).\n\nEvery field is priced against the caller’s own access: a scope you do not hold, a `record_visibility` wider than yours, or a `stock_visibility` or `project_visibility` wider than yours is a **403**. Owners and unrestricted admins are unaffected — the ceilings can only ever bite a caller who is themselves restricted. A narrowly-scoped `sk_` key cannot set `record_visibility` at all (a credential carries no such ceiling of its own, so there is nothing to bound the grant against); use a signed-in user.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Roles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "tint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 100
                    }
                  },
                  "record_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "assigned_unassigned",
                      "assigned"
                    ]
                  },
                  "stock_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "own_locations"
                    ]
                  },
                  "project_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "sales_office",
                      "assigned"
                    ]
                  },
                  "clinic_record_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "own_patients"
                    ]
                  },
                  "max_discount_percent": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Vendedor sucursal",
                "scopes": [
                  "conversations:read",
                  "conversations:write",
                  "leads:read",
                  "leads:write"
                ],
                "record_visibility": "assigned",
                "stock_visibility": "own_locations"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Vendedor sucursal",
                    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
                    "scopes": [
                      "conversations:read",
                      "conversations:write",
                      "leads:read",
                      "leads:write"
                    ],
                    "record_visibility": "assigned",
                    "stock_visibility": "own_locations",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/roles/{id}": {
      "get": {
        "summary": "Fetch one custom role",
        "description": "The role including both visibility ceilings. A role belonging to another workspace is a **404**, never a 403 — nothing here confirms a guessed id.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Roles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Vendedor sucursal",
                    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
                    "scopes": [
                      "conversations:read",
                      "conversations:write",
                      "leads:read",
                      "leads:write"
                    ],
                    "record_visibility": "assigned",
                    "stock_visibility": "own_locations",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a custom role",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied and at least one is required. `scopes` is a FULL REPLACEMENT array, not a merge — but only the scopes being ADDED are priced, so an admin who lacks `X` can still rename a role that already carries it.\n\nThe change takes effect on the next request of every member already on the role, including the caller.\n\n`record_visibility` is `all` | `assigned_unassigned` | `assigned` and narrows conversations, tickets and leads by assignee. `stock_visibility` is `all` | `own_locations` and narrows VEHICLE reads to the sucursales on the member’s own `workspace_member.location_ids` — a member on an `own_locations` role with no sucursales sees no stock at all, and a vehicle with no `location_id` is invisible to such a member by definition. `project_visibility` (Módulo Proyectos) is `all` | `sales_office` | `assigned` and narrows LEADS (and the proyectos list) to the proyectos the member works: `assigned` = the proyectos they are assigned to (ejecutivo de ventas), `sales_office` = those plus every proyecto served by a sala de ventas on their `location_ids` (jefe de sala). A lead the member owns stays visible. All three default to `all`. `max_discount_percent` (Módulo Proyectos) is the role’s límite de descuento: the largest effective discount, in percent over the list total, a member on the role may send on a cotización without approval. `null` (the default) is no limit; above the limit a member holding `cotizacion_discount:approve` must approve first. `clinic_record_visibility` (clinics) is `all` | `own_patients`: with `own_patients` a `clinic_record:read` holder opens only the clinical records of patients their linked professional attended or is assigned to (lifted by the clinic setting `clinic_records_shared_across_professionals`).\n\nEvery field is priced against the caller’s own access: a scope you do not hold, a `record_visibility` wider than yours, or a `stock_visibility` or `project_visibility` wider than yours is a **403**. Owners and unrestricted admins are unaffected — the ceilings can only ever bite a caller who is themselves restricted. A narrowly-scoped `sk_` key cannot set `record_visibility` at all (a credential carries no such ceiling of its own, so there is nothing to bound the grant against); use a signed-in user.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Roles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "tint": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "scopes": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 100
                    }
                  },
                  "record_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "assigned_unassigned",
                      "assigned"
                    ]
                  },
                  "stock_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "own_locations"
                    ]
                  },
                  "project_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "sales_office",
                      "assigned"
                    ]
                  },
                  "clinic_record_visibility": {
                    "type": "string",
                    "enum": [
                      "all",
                      "own_patients"
                    ]
                  },
                  "max_discount_percent": {
                    "type": [
                      "number",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 100
                  }
                }
              },
              "example": {
                "record_visibility": "assigned_unassigned"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated role",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d2d2d2d2-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Vendedor sucursal",
                    "description": "Ve solo sus propias conversaciones y el stock de su sucursal.",
                    "scopes": [
                      "conversations:read",
                      "conversations:write",
                      "leads:read",
                      "leads:write"
                    ],
                    "record_visibility": "assigned_unassigned",
                    "stock_visibility": "own_locations",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a custom role",
        "description": "A role that still has members is a **409**: unlinking them would resolve each one to their BUILT-IN tier with `record_visibility: all` and `stock_visibility: all`, i.e. silently promote them. Empty the role first, or pass `?reassign_to=<role id>` to move every member onto a named replacement in one step — the replacement is a grant and is priced on all three axes exactly as the body of a PUT is. `reassign_to` may not name the role being deleted (**400**), and reassignment to a built-in tier is not supported.\n\nThe delete also REVOKES every still-`pending` invitation that named the role, in the same transaction: that foreign key is `ON DELETE SET NULL`, so such an invitation would otherwise accept on its plain built-in tier. Accepted, expired and already-revoked invitations are untouched.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Roles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "reassign_to",
            "in": "query"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The role still has members; reassign them first"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/teams": {
      "get": {
        "summary": "List teams",
        "description": "Every team in the workspace, with a `total`. Unfiltered and unpaginated — workspaces have a handful of teams, not thousands.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "responses": {
          "200": {
            "description": "The workspace’s teams",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "cccccccc-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Ventas Providencia",
                      "brand": "Providencia",
                      "description": "El equipo que atiende el canal WhatsApp de la sucursal.",
                      "hours_mode": "override",
                      "hours": {
                        "mon": {
                          "on": true,
                          "start": "09:00",
                          "end": "19:00"
                        },
                        "tue": {
                          "on": true,
                          "start": "09:00",
                          "end": "19:00"
                        },
                        "sat": {
                          "on": true,
                          "start": "10:00",
                          "end": "14:00"
                        }
                      },
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a team",
        "description": "`name` is unique per workspace — a collision is a **409**, not a silent second team with the same name.\n\n**`hours` is only kept when `hours_mode` is `\"override\"`.** The mode defaults to `\"workspace\"`, so a create that sends windows without also setting the mode stores `null` and answers 201 as though it worked. Send both together. `hours` is a partial weekday map (`mon`…`sun`) of `{ on, start, end }`, each time `HH:MM`. A weekday you leave out is not covered rather than inherited — the override replaces the workspace week, it does not merge into it.\n\n`brand` is the free-text label that ties a team to a channel in the operator UI; it is not validated against anything.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "brand": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 2000
                  },
                  "hours_mode": {
                    "type": "string",
                    "enum": [
                      "workspace",
                      "override"
                    ]
                  },
                  "hours": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "mon": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "tue": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "wed": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "thu": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "fri": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "sat": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "sun": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      }
                    }
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Ventas Providencia",
                "brand": "Providencia",
                "hours_mode": "override",
                "hours": {
                  "mon": {
                    "on": true,
                    "start": "09:00",
                    "end": "19:00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "cccccccc-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas Providencia",
                    "brand": "Providencia",
                    "description": "El equipo que atiende el canal WhatsApp de la sucursal.",
                    "hours_mode": "override",
                    "hours": {
                      "mon": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "tue": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "sat": {
                        "on": true,
                        "start": "10:00",
                        "end": "14:00"
                      }
                    },
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A team with that name already exists in the workspace"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/teams/{id}": {
      "get": {
        "summary": "Fetch one team",
        "description": "The team including its `hours_mode` and any override windows.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "cccccccc-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas Providencia",
                    "brand": "Providencia",
                    "description": "El equipo que atiende el canal WhatsApp de la sucursal.",
                    "hours_mode": "override",
                    "hours": {
                      "mon": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "tue": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "sat": {
                        "on": true,
                        "start": "10:00",
                        "end": "14:00"
                      }
                    },
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update a team",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied and at least one is required.\n\nThe hours rules are not symmetric with create, so read them separately. Setting `hours_mode: \"workspace\"` CLEARS the stored override in the same write — the windows are gone, not parked, and flipping back to `\"override\"` later resumes with nothing. Conversely, sending `hours` while the mode is `\"workspace\"` stores windows that have no effect until the mode is switched. `hours` is a partial weekday map (`mon`…`sun`) of `{ on, start, end }`, each time `HH:MM`. A weekday you leave out is not covered rather than inherited — the override replaces the workspace week, it does not merge into it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "brand": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "hours_mode": {
                    "type": "string",
                    "enum": [
                      "workspace",
                      "override"
                    ]
                  },
                  "hours": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "mon": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "tue": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "wed": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "thu": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "fri": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "sat": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      },
                      "sun": {
                        "type": "object",
                        "properties": {
                          "on": {
                            "type": "boolean"
                          },
                          "start": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          },
                          "end": {
                            "type": "string",
                            "pattern": "^\\d{2}:\\d{2}$"
                          }
                        },
                        "required": [
                          "on",
                          "start",
                          "end"
                        ]
                      }
                    }
                  }
                }
              },
              "example": {
                "description": "Turno mañana y tarde."
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated team",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "cccccccc-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Ventas Providencia",
                    "brand": "Providencia",
                    "description": "Turno mañana y tarde.",
                    "hours_mode": "override",
                    "hours": {
                      "mon": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "tue": {
                        "on": true,
                        "start": "09:00",
                        "end": "19:00"
                      },
                      "sat": {
                        "on": true,
                        "start": "10:00",
                        "end": "14:00"
                      }
                    },
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a team",
        "description": "A hard delete with **fan-out you should expect**: memberships cascade away, and every `messaging_account.default_team_id`, `lead.team_id` and appointment `sales_team_id` pointing here is set to NULL. Channels that routed to this team silently stop routing anywhere.\n\n`ticket.team_id` is a bare column with no foreign key, so open tickets keep pointing at the deleted id and their coverage clock falls back to the workspace schedule. Reassign them before deleting if that matters.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/teams/{id}/members": {
      "get": {
        "summary": "List a team’s members",
        "description": "The membership rows, with `total` and `team_id` in the envelope. Each row carries `user_id`, `role`, `added_by` and `added_at` — this is the membership record, not the user profile, so names and emails have to be joined from `/memberships`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Team memberships",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e7e7e7e7-0000-4000-8000-000000000001",
                      "team_id": "cccccccc-0000-4000-8000-000000000001",
                      "user_id": "11111111-0000-4000-8000-000000000001",
                      "role": "lead",
                      "added_by": "11111111-0000-4000-8000-000000000001",
                      "added_at": "2026-01-10T13:05:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1,
                    "team_id": "cccccccc-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a member",
        "description": "`user_id` must already be an **active member of this workspace** — an unknown user, one from another workspace, or a deactivated one is refused with a 400. That check is what stops `teams:write` being used to graft an arbitrary user id onto a team, so it is deliberate rather than incidental validation.\n\n`role` defaults to `member`. Adding the same user twice is a **409** (one membership per user per team). `added_by` records the human who made the change and is NULL when an `sk_*` API key does it, since a key has no subject.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "user_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "role": {
                    "type": "string",
                    "enum": [
                      "lead",
                      "member"
                    ]
                  }
                },
                "required": [
                  "user_id"
                ]
              },
              "example": {
                "user_id": "e6e6e6e6-0000-4000-8000-000000000002",
                "role": "member"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "e7e7e7e7-0000-4000-8000-000000000001",
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "user_id": "e6e6e6e6-0000-4000-8000-000000000002",
                    "role": "member",
                    "added_by": "11111111-0000-4000-8000-000000000001",
                    "added_at": "2026-01-10T13:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "That user is already in this team"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/teams/{id}/members/{membershipId}": {
      "put": {
        "summary": "Change a member’s role",
        "description": "Moves a membership between `lead` and `member`. The role is a label on the team, not a permission grant: what a user may DO is decided by their workspace role and scopes, so promoting someone to `lead` here does not widen their access.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "membershipId",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "role": {
                    "type": "string",
                    "enum": [
                      "lead",
                      "member"
                    ]
                  }
                },
                "required": [
                  "role"
                ]
              },
              "example": {
                "role": "lead"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated membership",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e7e7e7e7-0000-4000-8000-000000000001",
                    "team_id": "cccccccc-0000-4000-8000-000000000001",
                    "user_id": "11111111-0000-4000-8000-000000000001",
                    "role": "lead",
                    "added_by": "11111111-0000-4000-8000-000000000001",
                    "added_at": "2026-01-10T13:05:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Remove a member",
        "description": "Removes the membership only — the user keeps their workspace account and any other team. Tickets already assigned to them keep the assignment: `ticket.assignee_user_id` is deliberately not a foreign key, so work in flight is not orphaned by a team change.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Teams"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "membershipId",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/digest/config": {
      "get": {
        "summary": "Get the Monday-digest configuration",
        "description": "The tenant's digest config (created with its defaults — enabled, Monday 08:00 America/Santiago, email on, WhatsApp OFF — on first read if no row exists yet; WhatsApp sends cost money, so an admin opts in with `whatsapp_enabled: true`, which submits the summary templates to Meta) alongside the RESOLVED recipient list: one entry per active workspace member, with their effective variant(s) (`owner`/`seller`), per-channel eligibility, opt-out state and normalized phone (or a skip reason when none is usable). A member with no explicit override shows the role-derived default (`source: \"role_default\"`); one who has been customized shows `source: \"explicit\"`. `whatsapp_templates` reports the WhatsApp templates: `status` is `off` | `no_whatsapp` | `pending` | `ready` | `attention` | `unknown`, with one `templates[]` row per (entry `owner`|`seller`, number) carrying `state` (`missing`|`pending`|`approved`|`rejected`|`blocked`) and `rejection_reason`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Digest"
        ],
        "responses": {
          "200": {
            "description": "The digest config and resolved recipient list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "config": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "tenant_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "enabled": {
                              "type": "boolean"
                            },
                            "send_weekday": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 7
                            },
                            "send_hour": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 23
                            },
                            "send_minute": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 59
                            },
                            "timezone": {
                              "type": "string"
                            },
                            "whatsapp_enabled": {
                              "type": "boolean"
                            },
                            "email_enabled": {
                              "type": "boolean"
                            },
                            "enabled_at": {
                              "type": "string"
                            },
                            "created_at": {
                              "type": "string"
                            },
                            "updated_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "tenant_id",
                            "enabled",
                            "send_weekday",
                            "send_hour",
                            "send_minute",
                            "timezone",
                            "whatsapp_enabled",
                            "email_enabled",
                            "enabled_at",
                            "created_at",
                            "updated_at"
                          ]
                        },
                        "recipients": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "user_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "variants": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "enum": [
                                    "owner",
                                    "seller"
                                  ]
                                }
                              },
                              "whatsapp_enabled": {
                                "type": "boolean"
                              },
                              "email_enabled": {
                                "type": "boolean"
                              },
                              "opted_out": {
                                "type": "boolean"
                              },
                              "phone_e164": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "phone_skip_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "no_phone",
                                  "unparsable"
                                ]
                              },
                              "source": {
                                "type": "string",
                                "enum": [
                                  "explicit",
                                  "role_default"
                                ]
                              }
                            },
                            "required": [
                              "user_id",
                              "variants",
                              "whatsapp_enabled",
                              "email_enabled",
                              "opted_out",
                              "phone_e164",
                              "phone_skip_reason",
                              "source"
                            ]
                          }
                        },
                        "whatsapp_templates": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "off",
                                "no_whatsapp",
                                "pending",
                                "ready",
                                "attention",
                                "unknown"
                              ]
                            },
                            "templates": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "entry": {
                                    "type": "string",
                                    "enum": [
                                      "owner",
                                      "seller"
                                    ]
                                  },
                                  "messaging_account_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": "string"
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "missing",
                                      "pending",
                                      "approved",
                                      "rejected",
                                      "blocked"
                                    ]
                                  },
                                  "rejection_reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "entry",
                                  "messaging_account_id",
                                  "name",
                                  "state",
                                  "rejection_reason"
                                ]
                              }
                            }
                          },
                          "required": [
                            "status",
                            "templates"
                          ]
                        }
                      },
                      "required": [
                        "config",
                        "recipients",
                        "whatsapp_templates"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "config": {
                      "id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "tenant_id": "20000000-0000-4000-8000-000000000002",
                      "enabled": true,
                      "send_weekday": 1,
                      "send_hour": 8,
                      "send_minute": 0,
                      "timezone": "America/Santiago",
                      "whatsapp_enabled": false,
                      "email_enabled": true,
                      "enabled_at": "2026-10-15T12:00:00.000Z",
                      "created_at": "2026-10-15T12:00:00.000Z",
                      "updated_at": "2026-10-15T12:00:00.000Z"
                    },
                    "recipients": [
                      {
                        "user_id": "20000000-0000-4000-8000-000000000001",
                        "variants": [
                          "owner"
                        ],
                        "whatsapp_enabled": true,
                        "email_enabled": true,
                        "opted_out": false,
                        "phone_e164": "+56911111111",
                        "phone_skip_reason": null,
                        "source": "role_default"
                      }
                    ],
                    "whatsapp_templates": {
                      "status": "off",
                      "templates": []
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update the Monday-digest configuration",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied. `recipients`, if present, is a batch of per-user overrides applied in the SAME request — each entry upserts the explicit `digest_recipient` row for that `user_id` (creating it on first customization), so a settings page can save the schedule and the roster edits together. `opted_out: true` opts a recipient out (same effect as them clicking the signed email link); `opted_out: false` clears a prior opt-out. Returns the same shape as the GET.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Digest"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "send_weekday": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 7
                  },
                  "send_hour": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 23
                  },
                  "send_minute": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 59
                  },
                  "timezone": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "whatsapp_enabled": {
                    "type": "boolean"
                  },
                  "email_enabled": {
                    "type": "boolean"
                  },
                  "recipients": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "user_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "variants": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "owner",
                              "seller"
                            ]
                          },
                          "maxItems": 2
                        },
                        "whatsapp_enabled": {
                          "type": "boolean"
                        },
                        "email_enabled": {
                          "type": "boolean"
                        },
                        "opted_out": {
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "user_id"
                      ]
                    },
                    "maxItems": 500
                  }
                }
              },
              "example": {
                "whatsapp_enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated digest config and resolved recipient list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "config": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "tenant_id": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "enabled": {
                              "type": "boolean"
                            },
                            "send_weekday": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 7
                            },
                            "send_hour": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 23
                            },
                            "send_minute": {
                              "type": "integer",
                              "minimum": 0,
                              "maximum": 59
                            },
                            "timezone": {
                              "type": "string"
                            },
                            "whatsapp_enabled": {
                              "type": "boolean"
                            },
                            "email_enabled": {
                              "type": "boolean"
                            },
                            "enabled_at": {
                              "type": "string"
                            },
                            "created_at": {
                              "type": "string"
                            },
                            "updated_at": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "id",
                            "tenant_id",
                            "enabled",
                            "send_weekday",
                            "send_hour",
                            "send_minute",
                            "timezone",
                            "whatsapp_enabled",
                            "email_enabled",
                            "enabled_at",
                            "created_at",
                            "updated_at"
                          ]
                        },
                        "recipients": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "user_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "variants": {
                                "type": "array",
                                "items": {
                                  "type": "string",
                                  "enum": [
                                    "owner",
                                    "seller"
                                  ]
                                }
                              },
                              "whatsapp_enabled": {
                                "type": "boolean"
                              },
                              "email_enabled": {
                                "type": "boolean"
                              },
                              "opted_out": {
                                "type": "boolean"
                              },
                              "phone_e164": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "phone_skip_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "no_phone",
                                  "unparsable"
                                ]
                              },
                              "source": {
                                "type": "string",
                                "enum": [
                                  "explicit",
                                  "role_default"
                                ]
                              }
                            },
                            "required": [
                              "user_id",
                              "variants",
                              "whatsapp_enabled",
                              "email_enabled",
                              "opted_out",
                              "phone_e164",
                              "phone_skip_reason",
                              "source"
                            ]
                          }
                        },
                        "whatsapp_templates": {
                          "type": "object",
                          "properties": {
                            "status": {
                              "type": "string",
                              "enum": [
                                "off",
                                "no_whatsapp",
                                "pending",
                                "ready",
                                "attention",
                                "unknown"
                              ]
                            },
                            "templates": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "entry": {
                                    "type": "string",
                                    "enum": [
                                      "owner",
                                      "seller"
                                    ]
                                  },
                                  "messaging_account_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "name": {
                                    "type": "string"
                                  },
                                  "state": {
                                    "type": "string",
                                    "enum": [
                                      "missing",
                                      "pending",
                                      "approved",
                                      "rejected",
                                      "blocked"
                                    ]
                                  },
                                  "rejection_reason": {
                                    "type": [
                                      "string",
                                      "null"
                                    ]
                                  }
                                },
                                "required": [
                                  "entry",
                                  "messaging_account_id",
                                  "name",
                                  "state",
                                  "rejection_reason"
                                ]
                              }
                            }
                          },
                          "required": [
                            "status",
                            "templates"
                          ]
                        }
                      },
                      "required": [
                        "config",
                        "recipients",
                        "whatsapp_templates"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "config": {
                      "id": "d1d1d1d1-0000-4000-8000-000000000001",
                      "tenant_id": "20000000-0000-4000-8000-000000000002",
                      "enabled": true,
                      "send_weekday": 1,
                      "send_hour": 8,
                      "send_minute": 0,
                      "timezone": "America/Santiago",
                      "whatsapp_enabled": true,
                      "email_enabled": true,
                      "enabled_at": "2026-10-15T12:00:00.000Z",
                      "created_at": "2026-10-15T12:00:00.000Z",
                      "updated_at": "2026-10-15T12:00:00.000Z"
                    },
                    "recipients": [
                      {
                        "user_id": "20000000-0000-4000-8000-000000000001",
                        "variants": [
                          "owner"
                        ],
                        "whatsapp_enabled": true,
                        "email_enabled": true,
                        "opted_out": false,
                        "phone_e164": "+56911111111",
                        "phone_skip_reason": null,
                        "source": "role_default"
                      }
                    ],
                    "whatsapp_templates": {
                      "status": "pending",
                      "templates": [
                        {
                          "entry": "owner",
                          "messaging_account_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "name": "resumen_semanal_v2",
                          "state": "pending",
                          "rejection_reason": null
                        },
                        {
                          "entry": "seller",
                          "messaging_account_id": "a1a1a1a1-0000-4000-8000-000000000001",
                          "name": "resumen_vendedor_v2",
                          "state": "pending",
                          "rejection_reason": null
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/tags": {
      "get": {
        "summary": "List tags",
        "description": "`q` filters by name. `limit` defaults to **50** and caps at 200 — there is no cursor, so a workspace with more tags than the limit simply cannot page past it; narrow with `q` instead.\n\n`with_contact_counts=true` adds how many contacts carry each tag. It is an aggregate over the join, so leave it off for a picker and turn it on for a management screen.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "q",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "with_contact_counts",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "12121212-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "garantia",
                      "display_name": "Garantía",
                      "usage_count": 1,
                      "created_at": "2026-09-22T11:06:38.372Z"
                    },
                    {
                      "id": "12121212-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "urgente",
                      "display_name": "Urgente",
                      "usage_count": 4,
                      "created_at": "2026-09-22T11:06:38.372Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a tag, or get the existing one",
        "description": "Idempotent by slug, and **the status code is how you tell which happened**: 201 when the tag was created, **200 when one with that slug already existed** and is being returned instead. Neither is an error, so a UI can submit a freshly typed name without checking first.\n\n`name` is slugified to form the identity; `display_name` is what people see and defaults to `name` as typed. Submitting an existing slug with a different `display_name` returns the STORED row unchanged — this endpoint will not rename an existing tag, `PUT /tags/{id}` does that.\n\nA `name` with no alphanumeric characters slugifies to nothing and is a 400. Concurrent creates of the same new name are safe: the loser of the race gets the winner’s row rather than an error.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Garantía",
                "display_name": "Garantía"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tag that already existed with this slug",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "12121212-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "garantia",
                    "display_name": "Garantía",
                    "usage_count": 1,
                    "created_at": "2026-09-22T11:06:38.372Z"
                  }
                }
              }
            }
          },
          "201": {
            "description": "The newly created tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "12121212-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "garantia",
                    "display_name": "Garantía",
                    "usage_count": 1,
                    "created_at": "2026-09-22T11:06:38.372Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/tags/suggest": {
      "get": {
        "summary": "Ask the model to suggest tags for a conversation",
        "description": "Reads the conversation’s recent transcript and the workspace’s existing tags, and returns suggestions as `{ name, display_name, existing, tag_id }`.\n\n`existing: true` means the slug is already a tag here and `tag_id` is set, so it can be attached directly. `existing: false` rows have a null `tag_id` and are attached by NAME, which creates them on first use — the suggestion itself creates nothing.\n\nSuggestions are deduplicated by slug, so the model proposing both `Prioridad` and `prioridad` yields one row. Nothing is attached and nothing is stored; this is advisory.\n\nResults are cached server-side per conversation, keyed off the newest message — a fresh message rotates the key automatically, so repeat opens of an unchanged conversation are served from cache with no LLM call. Pass `force=true` to bypass a live cache entry and recompute, overwriting it — this backs an explicit \"refresh suggestions\" action; do not set it on every load.\n\nOn a cache miss this is one LLM call over the conversation transcript — not free, and not something to poll. Registered before `/{id}` so `suggest` is never read as a tag id.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "conversation_id",
            "in": "query"
          },
          {
            "schema": {
              "anyOf": [
                {
                  "type": "boolean"
                },
                {
                  "type": "string"
                }
              ]
            },
            "required": false,
            "name": "force",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Suggested tags",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "name": "garantia",
                      "display_name": "Garantía",
                      "existing": true,
                      "tag_id": "12121212-0000-4000-8000-000000000001"
                    },
                    {
                      "name": "coordinacion-de-visita",
                      "display_name": "coordinacion-de-visita",
                      "existing": false,
                      "tag_id": null
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/tags/{id}": {
      "get": {
        "summary": "Fetch one tag",
        "description": "The tag by id, without the contact count the list can add.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "12121212-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "garantia",
                    "display_name": "Garantía",
                    "usage_count": 1,
                    "created_at": "2026-09-22T11:06:38.372Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Rename a tag’s display name",
        "description": "`display_name` is the ONLY editable field, and it is required. The slug `name` is permanent: it is the identity every attachment and every create-by-name call resolves through, so renaming it would detach the tag from itself.\n\nIn practice that means a tag can be relabelled for people without anything breaking, but its canonical name stays whatever it was first typed as. To change the slug, create a new tag and re-attach.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "display_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "required": [
                  "display_name"
                ]
              },
              "example": {
                "display_name": "Garantía extendida"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated tag",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "12121212-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "garantia",
                    "display_name": "Garantía extendida",
                    "usage_count": 1,
                    "created_at": "2026-09-22T11:06:38.372Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a tag",
        "description": "Removes the tag and every attachment to it, across conversations and contacts alike. A 404 for an id that is not there.\n\nIt does not stay deleted if anything still uses the name: attaching by name re-creates it, and so does an agent acting on a suggestion. A tag that keeps coming back is being written by something, not resurrected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Tags"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/vehicles/stats": {
      "get": {
        "summary": "Aggregate counts for the whole lot",
        "description": "The stock health header — counts across the workspace’s inventory, not a page of vehicles.\n\n`by_tenencia` (Tenencia, ADR 0025) splits the lot four ways: `propio`, `consignacion_en_local`, `consignacion_virtual` and `consignacion_sin_contrato` (consigned on the vehicle row with NO contract in `estado=activo`). The four PARTITION the lot — they sum to `total` — and each equals the length of the list its chip opens: `GET /vehicles?active=false&tenencia=…[&consignacion_modalidad=…]`, the same predicate builder serving both. Like `by_location` and `total`, they count the WHOLE lot rather than active stock only, which is what the Stock screen shows (it sends `active=false`).\n\n`propio_sin_clasificar` (Phase 2, tenencia_source, 20270903000000) is a SUB-COUNT of `propio`, NOT a fifth bucket — the four above still sum to `total`. It is the cars nobody has ever classified (`tenencia_source=default`), provably coextensive with \"sin clasificar\" (a consignación can never be `default`). Measured on production 2026-08-31: 903 of 967 vehicles carried `propio` with nobody having said so — that is what this number answers, rather than leaving `propio` an unresolved warning. Opens with `GET /vehicles?active=false&tenencia=propio&tenencia_source=default`.\n\n`sin_costo` (ADR 0114 §8, «Sin costo») counts the active, non-vendido cars with NO acquisition — no nota de compra, contrato de consignación or parte de pago — so their cost is unknown. Opens with `GET /vehicles?view=sin_costo`, the same predicate. A statement about the cost basis: ABSENT (not 0) for a caller without `dealership_economics:read`.\n\n`mandato_sin_firmar` (ADR 0114 §5) counts the cars whose consignación (activa, or vendida) has an emitted mandato and no signed copy filed in the expediente. Opens with `GET /vehicles?view=mandato_sin_firmar`, the same predicate.\n\nThe «Datos por completar» and «Pendientes» facet counts of the Stock filters (ADR 0114) — each is the length of the list it opens, built from the same predicate, over the whole lot like `total`:\n- `needs_review` — «Revisar marca/modelo»: the marca or modelo did not match the catalogue on save and waits for a person. Opens with `GET /vehicles?active=false&needs_review=true`.\n- `sin_sucursal` — «Sin sucursal»: no sucursal assigned. Opens with `GET /vehicles?active=false&sin_sucursal=true`. Always 0 for a member who only sees their own sucursales.\n- `sin_estacionamiento` — «Sin estacionamiento»: no estacionamiento in the patio. Opens with `GET /vehicles?active=false&sin_estacionamiento=true`.\n- `source_deactivated` — «Retirados del portal»: the portal withdrew the car, it is deactivated and still `disponible`, so nobody has said whether it was sold. Opens with `GET /vehicles?view=source_deactivated`.\n- `papers_expired` — «Papeles vencidos»: cars in stock (active, not vendido) holding a paper (SOAP, permiso de circulación, revisión técnica or a named paper) whose «Vence» day has passed and that no later paper of the same kind replaces. Opens with `GET /vehicles?papers_expired=true`.\n- `papers_expiring` — «Papeles por vencer»: cars in stock holding a paper whose «Vence» day is today or within the next 30 days. Opens with `GET /vehicles?papers_expiring=true`. A car can be in both.\n\nEvery count respects the caller’s sucursal scope (ADR 0102), the same ceiling the list applies. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "Aggregate stock counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "total": 7,
                    "active": 7,
                    "by_status": {
                      "disponible": 6,
                      "reservado": 1
                    },
                    "published": 0,
                    "with_error": 0,
                    "unpublished": 7,
                    "stale": 0,
                    "sin_costo": 7,
                    "mandato_sin_firmar": 1,
                    "needs_review": 1,
                    "sin_sucursal": 2,
                    "sin_estacionamiento": 4,
                    "source_deactivated": 0,
                    "papers_expired": 1,
                    "papers_expiring": 2,
                    "by_location": {
                      "b1b1b1b1-0000-4000-8000-000000000001": 3,
                      "b1b1b1b1-0000-4000-8000-000000000002": 1,
                      "b1b1b1b1-0000-4000-8000-000000000003": 1
                    },
                    "by_tenencia": {
                      "propio": 7,
                      "consignacion_en_local": 0,
                      "consignacion_virtual": 0,
                      "consignacion_sin_contrato": 0,
                      "propio_sin_clasificar": 7
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/publications": {
      "get": {
        "summary": "Publications for many vehicles at once",
        "description": "The batched read behind the inventory list: pass `ids` and get every vehicle’s publications in one call instead of N. Capped at 100 ids per request — page the list and batch per page.\n\nNote `deleted` publications are tombstones rather than absences, so a vehicle that was unpublished still has rows here; read each row’s status rather than treating presence as \"published\". Each row also carries the per-portal push status of the last edit: `last_push_status` (`updating` | `updated` | `failed` | null), `last_push_at`, `last_push_error` (the portal’s reason when `failed`), `last_push_fields` and `push_delivery` (`api` | `feed` | `extension`; Yapo is `feed`: `updated` means the change is in the feed it reads). See `GET /vehicles/{id}/publications`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1
            },
            "required": true,
            "name": "ids",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Publications keyed by vehicle",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "e1e1e1e1-0000-4000-8000-000000000001": [
                      {
                        "id": "88888888-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "marketplace_account_id": "33333333-0000-4000-8000-000000000001",
                        "provider": "mercadolibre",
                        "external_id": "MLC-1234567890",
                        "status": "published",
                        "origin": "vitrina",
                        "permalink": "https://articulo.mercadolibre.cl/MLC-1234567890",
                        "last_synced_at": "2026-09-19T10:00:00.000Z",
                        "last_published_at": "2026-09-16T09:00:00.000Z",
                        "last_error": null,
                        "close_retry_count": 0,
                        "portal_state": "active",
                        "portal_state_at": "2026-09-19T10:00:00.000Z",
                        "last_push_status": "updated",
                        "last_push_at": "2026-09-19T09:58:00.000Z",
                        "last_push_error": null,
                        "last_push_fields": [
                          "priceAmount"
                        ],
                        "push_delivery": "api",
                        "raw_provider_payload": null,
                        "created_at": "2026-09-16T09:00:00.000Z",
                        "updated_at": "2026-09-19T10:00:00.000Z"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/publish-bulk": {
      "post": {
        "summary": "Publish many vehicles to one portal",
        "description": "N vehicles × ONE portal — `integration_id` is singular. To publish to several portals, call this once per portal.\n\nEach vehicle is isolated: the response is a per-vehicle outcome list, and a **200 does not mean every vehicle published**. `status` is `published`, `already_published` (a live ad already existed and nothing was sent), `skipped` (never reached the portal — vendido, inactive or gone; `reason` says which), `error` (`error` says why) or, for `facebook_marketplace` and `chileautos_panel` (see `POST /vehicles/{id}/publish`), `pending`: the portal call was queued and the real outcome arrives later via `vehicle.published` / `vehicle.publish_failed`. Up to 100 vehicles per call, and unlike the close-ads path this runs the portal round-trips synchronously, so a full batch is a slow request. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "vehicle_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "integration_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "vehicle_ids",
                  "integration_id"
                ]
              },
              "example": {
                "vehicle_ids": [
                  "e1e1e1e1-0000-4000-8000-000000000001",
                  "e1e1e1e1-0000-4000-8000-000000000002"
                ],
                "integration_id": "33333333-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-vehicle publish outcomes",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                      "status": "published",
                      "external_id": "MLC-1234567890"
                    },
                    {
                      "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                      "status": "skipped",
                      "reason": "missing_photos"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/{id}/close-ads": {
      "post": {
        "summary": "Close all of a vehicle’s ads",
        "description": "Cierre de avisos: removes the vehicle from EVERY connected portal at once. Contrast `DELETE /vehicles/{id}/publications/{pubId}`, which removes one publication synchronously — this closes the whole vehicle and is enqueued.\n\nAnswers `{ enqueued: true, vehicle_id }` immediately. That is an acknowledgement, not a result: the retry-backed pipeline does the portal work afterwards because portal removal fails often enough that doing it inline would strand ads. Poll the vehicle’s publications for the real state. It is enqueued with the `sold` intent. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ enqueued, vehicle_id }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "enqueued": true,
                    "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/vehicles/{id}/duplicate-candidates": {
      "get": {
        "summary": "Possible duplicates of this car",
        "description": "Posible duplicado: other cars of the workspace that share this car’s photos and specs but whose identifiers conflict in a way a person could have typed. `reason` is `plate_typo` (both plates present, one character apart) or `invalid_vin` (at least one VIN is implausible: not 17 characters, a non-alphanumeric character, an I/O/Q, or all digits). The nightly photo reconciliation records them; it never merges such a pair on its own.\n\nOnly OPEN candidates are listed: not dismissed, not resolved by a merge, and with both cars live (not merged, not deleted). The car may sit on either side of a pair; `other_vehicle` is always the OTHER car, with `lead_count` = the lead interests pointing at it. Act on a candidate with `POST /vehicles/{id}/merge` or `POST /vehicles/duplicate-candidates/{candidateId}/dismiss`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Open duplicate candidates of the car",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0b7c4e2a-5d1f-4a8e-9c3b-2f6d8e1a4b70",
                      "reason": "plate_typo",
                      "detected_at": "2026-10-26T06:12:40.000Z",
                      "other_vehicle": {
                        "id": "88f2fa25-3c71-4e0b-9a4d-6b1e2c7d5f90",
                        "title": "MINI Cooper S 2017",
                        "plate_normalized": "JLGJ42",
                        "vin": "25423423423131313",
                        "status": "disponible",
                        "active": true,
                        "price_clp": 15990000,
                        "photo_url": "https://cdn.example.com/vehicles/88f2fa25/1.jpg",
                        "lead_count": 5,
                        "created_at": "2026-09-02T14:05:11.000Z"
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/merge": {
      "post": {
        "summary": "Merge two cars into one",
        "description": "Fusionar: collapse this car and `other_vehicle_id` into ONE car. The survivor is chosen by the platform, never by the caller: an active car beats an inactive one, then the ERP stock-of-record row beats a portal row, then the car with more activity (leads, alerts, publications), then the older one. Every lead, reserva, documento, publicación, cita and cost line of the loser moves onto the survivor; the loser stops appearing in Stock. It cannot be undone through the API.\n\nAny two live cars of the workspace may be merged; a duplicate candidate is not required. Merging resolves every candidate naming the loser. 404 when either car is unknown, merged or deleted; 409 when both ids are the same car. Answers `{ survivor_id, loser_id }`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "other_vehicle_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "The other car of the workspace to merge with this one."
                  }
                },
                "required": [
                  "other_vehicle_id"
                ],
                "additionalProperties": false
              },
              "example": {
                "other_vehicle_id": "88f2fa25-3c71-4e0b-9a4d-6b1e2c7d5f90"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`{ survivor_id, loser_id }`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "survivor_id": "3b1f8a52-9d34-4a7e-8f2c-1a2b3c4d5e6f",
                    "loser_id": "88f2fa25-3c71-4e0b-9a4d-6b1e2c7d5f90"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/vehicles/duplicate-candidates/{candidateId}/dismiss": {
      "post": {
        "summary": "Dismiss a possible duplicate",
        "description": "«No es el mismo auto»: the two cars of the candidate are different cars. The pair leaves `GET /vehicles/{id}/duplicate-candidates` and the Stock list’s `possible_duplicate` flag, and the reconciliation never flags it again. Idempotent. 404 when the candidate is not in this workspace. Answers `{ id }`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "candidateId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ id }` of the dismissed candidate",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0b7c4e2a-5d1f-4a8e-9c3b-2f6d8e1a4b70"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/interest": {
      "get": {
        "summary": "Who is asking about this car",
        "description": "The interest rollup: distinct contacts, how many became named leads, a per-portal breakdown of where they came from, first and last contact, and days in stock.\n\nIt answers \"is anyone interested NOW\", which is why a **sold vehicle rolls up empty** (ADR 0030) rather than showing its historical interest — use `/activity` for the history. No contact ids are echoed, deliberately: the endpoint reports demand without becoming a way to enumerate the contact table. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The interest rollup",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "distinct_contacts": 2,
                    "named_leads": 1,
                    "by_portal": [
                      {
                        "portal": "website",
                        "contacts": 2
                      }
                    ],
                    "first_contact_at": "2026-09-16T12:00:00.000Z",
                    "last_contact_at": "2026-09-19T15:30:00.000Z",
                    "anonymous_interest": 1,
                    "in_stock_since": "2026-09-15T18:34:48.575Z",
                    "time_in_stock_days": 6,
                    "is_sold": false,
                    "lifetime": {
                      "distinct_contacts": 2,
                      "named_leads": 1,
                      "anonymous_interest": 1,
                      "first_contact_at": "2026-09-16T12:00:00.000Z",
                      "last_contact_at": "2026-09-19T15:30:00.000Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/activity": {
      "get": {
        "summary": "This car’s lead timeline",
        "description": "Every named lead that asked about this vehicle, merged with its anonymous Yapo phone-reveals, newest first. This is the car’s HISTORY, so unlike `/interest` a sold vehicle keeps its timeline.\n\nEach lead row carries `conversation_id`: the chat to open for that person (the lead's own conversation, else the contact's most recent one, e.g. the first-touch WhatsApp after a portal inquiry). Null when there is none.\n\nThe Stock detail screen merges this with `/vehicles/{id}/history` (the change log) into one \"Leads y cambios\" feed — they are two endpoints because they are two different kinds of event. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Lead and reveal events, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "kind": "lead",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "lead_id": "99999999-0000-4000-8000-000000000002",
                      "conversation_id": "99999999-0000-4000-8000-000000000003",
                      "portal": "website",
                      "at": "2026-09-19T15:30:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/{id}/busquedas": {
      "get": {
        "summary": "Who is waiting for this car (ADR 0109 §4)",
        "description": "Which open Búsquedas (customer wants) does THIS unit answer, graded live — `calza` (every bound holds) or `calza_parcial` (the name and years hold; the budget or the kilometres are over by no more than the workspace tolerance, with the difference stated). A want recorded AFTER the car arrived is never missed — this grades the CURRENT row on every call, not an activity log from intake.\n\n`notice` is what became of the notice about THIS car, or `null` when the customer has not heard about it yet. Búsqueda notices go out automatically. `notice.state` is `notified` or `dismissed` (the pair was considered and deliberately not sent); `notice.at` is when; `notice.channel` is the channel it went out on (`whatsapp`, `email`, `instagram`, `voice`, or `human_self` when the want was reserved by a member and only that member was told). `notice.conversation_id` + `notice.message_id` open the exact thread and message the notice went out in — both `null` when the notice has no thread of its own. `notice.replied_at` is the customer’s first message in that thread after the notice (`null` = no reply yet, or no thread to read). `already_notified` is `notice != null`.\n\n⚠ SCOPE IS `followups:read`, not `marketplace:read` — this reports CRM demand, not stock. ⚠ TENANT-WIDE, not row-visibility-gated: a shared review sheet about a unit in the shared stock. `contact.name` / `contact.phone` are further gated on `contacts:read` — a caller without it still sees the full worklist, minus the customer’s name and phone. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Open Búsquedas this car answers, graded, newest-expiring first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "subscription_id": "5b5b5b5b-0000-4000-8000-000000000001",
                      "contact_id": "22222222-0000-4000-8000-000000000001",
                      "contact": {
                        "id": "22222222-0000-4000-8000-000000000001",
                        "name": "Camila Rojas",
                        "phone": "+56912345678"
                      },
                      "lead_id": "99999999-0000-4000-8000-000000000001",
                      "owner": {
                        "user_id": "33333333-0000-4000-8000-000000000001",
                        "name": "Valentina Soto",
                        "avatar_url": null
                      },
                      "status": "active",
                      "trigger_key": "vehicle_in_stock",
                      "criteria": {
                        "make": "Mazda",
                        "model": "CX-30",
                        "year_min": 2021,
                        "max_price": 18000000
                      },
                      "note": null,
                      "sought_label": "Mazda CX-30 · 2021 en adelante",
                      "expires_at": "2026-10-19T12:00:00.000Z",
                      "days_left": 27,
                      "notice_count": 0,
                      "verdict": "calza_parcial",
                      "differences": [
                        {
                          "bound": "budget",
                          "stated": 18000000,
                          "actual": 18900000,
                          "over": 900000,
                          "overFraction": 0.05,
                          "text": "Está $900.000 sobre el presupuesto de $18.000.000"
                        }
                      ],
                      "already_notified": false,
                      "notice": null
                    },
                    {
                      "subscription_id": "5b5b5b5b-0000-4000-8000-000000000002",
                      "contact_id": "11111111-0000-4000-8000-000000000002",
                      "contact": {
                        "id": "11111111-0000-4000-8000-000000000002",
                        "name": "Tomás Herrera",
                        "phone": "+56911112222"
                      },
                      "lead_id": null,
                      "owner": null,
                      "status": "open",
                      "trigger_key": "vehicle_in_stock",
                      "criteria": {
                        "make": "Mazda",
                        "model": "CX-30"
                      },
                      "note": null,
                      "sought_label": "Mazda CX-30",
                      "expires_at": "2026-12-01T12:00:00.000Z",
                      "days_left": 70,
                      "notice_count": 1,
                      "verdict": "calza",
                      "differences": [],
                      "already_notified": true,
                      "notice": {
                        "state": "notified",
                        "at": "2026-09-29T17:02:00.000Z",
                        "channel": "whatsapp",
                        "conversation_id": "7c7c7c7c-0000-4000-8000-000000000001",
                        "message_id": "8d8d8d8d-0000-4000-8000-000000000001",
                        "replied_at": "2026-09-29T17:10:41.000Z"
                      }
                    }
                  ],
                  "meta": {
                    "total": 2
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/vehicles/photo-mirror/report": {
      "post": {
        "summary": "Preview the photo-mirror backfill",
        "description": "Dry run — modifies NOTHING. Counts the vehicles and photos still hotlinking a source CDN, grouped by origin integration.\n\nMirroring photos into Vitrina storage is the first precondition for retiring a source (ADR 0031): stock whose images still live on the source’s CDN breaks the moment that source is switched off. Takes no body. Scope: `stock_photo_mirror:read`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "What is still source-hosted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "scanned_vehicles": 7,
                    "vehicles_pending": 2,
                    "photos_pending": 5,
                    "by_integration": [
                      {
                        "origin_integration_id": null,
                        "source": "manual",
                        "vehicles": 2,
                        "photos": 5
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/photo-mirror/execute": {
      "post": {
        "summary": "Mirror a batch of photos into Vitrina storage",
        "description": "Copies up to `limit` vehicles’ photos (default 25, hard cap 200 per run). Deliberately bounded so a large lot is worked in chunks rather than one long request — call it repeatedly until the report comes back clean. Scope: `stock_photo_mirror:write`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                }
              },
              "example": {
                "limit": 25
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What this run mirrored",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "mirrored_vehicles": 2,
                    "mirrored_photos": 5,
                    "failures": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/photo-mirror/sweep": {
      "post": {
        "summary": "Mirror every remaining hotlink in one call",
        "description": "The unbounded sibling of `/execute`: no `limit`, and the scope is every ACTIVE vehicle in the workspace, so it is the call that drives the lot to zero rather than chipping at it. A large lot therefore makes this a LONG request — `/execute` is still the right tool when you need bounded runs.\n\nNot necessarily complete in one pass: failures come back as an actionable per-vehicle/per-url list and `all_mirrored` says whether anything is left. **Re-run until `all_mirrored` is true** — repeating it is safe, because a photo already mirrored is reused rather than fetched again.\n\nTakes an EMPTY body, strictly: the sweep is tenant-wide so there is nothing to parametrise, and an unknown field is a 400 rather than a silently ignored bound. Scope: `stock_photo_mirror:write`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              },
              "example": {}
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the sweep mirrored, and what is left",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "mirrored_vehicles": 2,
                    "mirrored_photos": 5,
                    "failures": [],
                    "all_mirrored": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/dedup-backfill/report": {
      "post": {
        "summary": "Preview the existing-stock dedup",
        "description": "Dry run — modifies NOTHING. Returns the proposed merges (Fusiones), the pairs it will not decide (Conflictos), and a note on photo-net coverage. Takes no body.\n\nThis is the review artefact a human reads before approving: the execute endpoint takes its `approve` list in exactly the shape these proposals come back in. Scope: `stock_dedup:read`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "responses": {
          "200": {
            "description": "Proposed merges and conflicts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "scanned_vehicles": 7,
                    "proposals": [
                      {
                        "survivor_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "loser_id": "e1e1e1e1-0000-4000-8000-000000000002",
                        "reason": "same_vin"
                      }
                    ],
                    "conflicts": [],
                    "photo_reconciliation": {
                      "included": true,
                      "deferred": false,
                      "engine_available": true,
                      "reason": "Same-photo duplicates were previewed inline because the pHash engine is present in this context.",
                      "proposals": []
                    },
                    "summary": {
                      "proposed_merges": 1,
                      "clusters": 1,
                      "conflicts": 0
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/dedup-backfill/execute": {
      "post": {
        "summary": "Apply the reviewed dedup merges",
        "description": "Merges duplicate vehicles through the standard merge machinery.\n\n`approve` is an APPROVAL FILTER, not a merge command: execute re-runs the scan and applies only pairs the fresh scan still proposes on its own, so a stale pair from an old report is skipped rather than forced through.\n\n**Omitting `approve` and sending `approve: []` are different.** Omit it to apply every currently-proposed merge; send an empty array to approve nothing and merge zero. The dangerous mistake is sending `[]` meaning \"no filter\". Up to 1000 pairs. Scope: `stock_dedup:write`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approve": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "survivor_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "loser_id": {
                          "type": "string",
                          "format": "uuid"
                        }
                      },
                      "required": [
                        "survivor_id",
                        "loser_id"
                      ]
                    },
                    "maxItems": 1000
                  }
                }
              },
              "example": {
                "approve": [
                  {
                    "survivor_id": "e1e1e1e1-0000-4000-8000-000000000001",
                    "loser_id": "e1e1e1e1-0000-4000-8000-000000000002"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was merged",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "merged": 1,
                    "skipped": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/parity-report": {
      "post": {
        "summary": "Compare native stock against the live source",
        "description": "Read-only certification: compares the native `vehicle` table with the LIVE Veekls API for one integration — counts, matched and missing vehicles, and field-level diffs on price, year, odometer, photo count and plate.\n\nThis is the evidence step in the decommission sequence: mirror photos → **parity** → repoint consumers → disable sync → retire the source → cancel the vendor contract. Modifies nothing, and takes the read scope (`marketplace:read`) accordingly. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integration_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "integration_id"
                ]
              },
              "example": {
                "integration_id": "33333333-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The parity report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "integration_id": "33333333-0000-4000-8000-000000000001",
                    "generated_at": "2026-09-20T09:00:00.000Z",
                    "summary": {
                      "native_total": 40,
                      "native_active": 38,
                      "veekls_total": 41,
                      "matched": 37,
                      "native_only": 1,
                      "veekls_only": 4,
                      "vehicles_with_diffs": 2
                    },
                    "native_only_items": [
                      {
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "external_id": "VK-10021",
                        "plate_normalized": "FGXY21",
                        "active": true,
                        "deactivated_by": null
                      }
                    ],
                    "native_only_truncated": false,
                    "veekls_only_items": [
                      {
                        "external_id": "VK-10099",
                        "plate": "HJKL34",
                        "price": 8990000,
                        "year": 2019,
                        "odometer": 60000
                      }
                    ],
                    "veekls_only_truncated": false,
                    "diffs": [
                      {
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000002",
                        "external_id": "VK-10022",
                        "plate_normalized": "MNPQ56",
                        "diffs": [
                          {
                            "field": "price",
                            "native_value": 13990000,
                            "veekls_value": 13490000
                          }
                        ],
                        "protected_diffs": []
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/source-retirement/report": {
      "post": {
        "summary": "Preview retiring a source",
        "description": "Dry run for Retiro de fuente (ADR 0031): the status of each precondition plus the review list of what would be affected, for one `integration_id`. Modifies nothing. Scope: `stock_retirement:read`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integration_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "integration_id"
                ]
              },
              "example": {
                "integration_id": "33333333-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Preconditions and the review list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "integration_id": "33333333-0000-4000-8000-000000000001",
                    "provider": "veekls",
                    "vehicles_with_origin": 12,
                    "photos": {
                      "all_mirrored": true,
                      "pending": 0
                    },
                    "sync_still_enabled": false,
                    "hidden_by_source": [
                      {
                        "vehicle_id": "e1e1e1e1-0000-4000-8000-000000000001",
                        "title": null,
                        "make": "Toyota",
                        "model": "Corolla",
                        "year": 2022,
                        "plate": "FGXY21",
                        "status": "disponible"
                      }
                    ],
                    "already_retired": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/vehicles/source-retirement/execute": {
      "post": {
        "summary": "Retire a source",
        "description": "**A once-per-source, admin-level decommission — not a casual button.** It severs the workspace’s stock from an integration for good, and it is what `DELETE /marketplaces/{id}` refuses to let you skip: deleting a marketplace account that still originates vehicles is blocked precisely so this runs first.\n\n`revive` is the dealer’s decision from the review list — the vehicle ids to keep. **Everything not named stays hidden.** Up to 200 ids, and omitting the field revives nothing. Read the report and decide deliberately; there is no un-retire endpoint. Scope: `stock_retirement:write`. Automotive-vertical only: a workspace on another vertical gets 403 here however its scopes are set.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Vehicles"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "integration_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "revive": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "maxItems": 200
                  }
                },
                "required": [
                  "integration_id"
                ]
              },
              "example": {
                "integration_id": "33333333-0000-4000-8000-000000000001",
                "revive": [
                  "e1e1e1e1-0000-4000-8000-000000000001"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the retirement did",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "integration_id": "33333333-0000-4000-8000-000000000001",
                    "retired": 11,
                    "revived": 1,
                    "revive_failures": [],
                    "kept_hidden": [],
                    "already_retired": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/slas": {
      "get": {
        "summary": "List SLA policies",
        "description": "Every policy in the workspace. Unfiltered and unpaginated — a workspace configures a handful of policies, not thousands.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "SLAs"
        ],
        "responses": {
          "200": {
            "description": "SLA policies",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d7d7d7d7-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Soporte estándar",
                      "description": "Primera respuesta en media hora hábil, resolución en un día.",
                      "target_first_response_minutes": 30,
                      "target_resolution_minutes": 1440,
                      "business_hours_only": true,
                      "applies_to": {
                        "channel": "whatsapp"
                      },
                      "active": true,
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an SLA policy",
        "description": "Both targets are optional and independent: a policy can set only a first-response target, only a resolution target, or both. `name` is unique per workspace (a collision is a **409**).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "SLAs"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "target_first_response_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "target_resolution_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "business_hours_only": {
                    "type": "boolean"
                  },
                  "applies_to": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "Targeting filter; a ticket is covered when ANY listed criterion matches. Keys: team_ids, messaging_account_ids, channels, pipeline_ids (ticket boards), stage_ids, priorities (emergencia | alta | normal). first_response_by: \"human\" measures the team's first reply apart from the AI's."
                  },
                  "active": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Soporte estándar",
                "target_first_response_minutes": 30,
                "target_resolution_minutes": 1440,
                "business_hours_only": true,
                "applies_to": {
                  "channel": "whatsapp"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Soporte estándar",
                    "description": "Primera respuesta en media hora hábil, resolución en un día.",
                    "target_first_response_minutes": 30,
                    "target_resolution_minutes": 1440,
                    "business_hours_only": true,
                    "applies_to": {
                      "channel": "whatsapp"
                    },
                    "active": true,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "A policy with that name already exists in the workspace"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/slas/{id}": {
      "get": {
        "summary": "Fetch one SLA policy",
        "description": "The policy — both targets, `applies_to` and `business_hours_only`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "SLAs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Soporte estándar",
                    "description": "Primera respuesta en media hora hábil, resolución en un día.",
                    "target_first_response_minutes": 30,
                    "target_resolution_minutes": 1440,
                    "business_hours_only": true,
                    "applies_to": {
                      "channel": "whatsapp"
                    },
                    "active": true,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update an SLA policy",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied and at least one is required.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "SLAs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "target_first_response_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "target_resolution_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "exclusiveMinimum": 0
                  },
                  "business_hours_only": {
                    "type": "boolean"
                  },
                  "applies_to": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "Targeting filter; a ticket is covered when ANY listed criterion matches. Keys: team_ids, messaging_account_ids, channels, pipeline_ids (ticket boards), stage_ids, priorities (emergencia | alta | normal). first_response_by: \"human\" measures the team's first reply apart from the AI's."
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "target_first_response_minutes": 20
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated policy",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d7d7d7d7-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Soporte estándar",
                    "description": "Primera respuesta en media hora hábil, resolución en un día.",
                    "target_first_response_minutes": 20,
                    "target_resolution_minutes": 1440,
                    "business_hours_only": true,
                    "applies_to": {
                      "channel": "whatsapp"
                    },
                    "active": true,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an SLA policy",
        "description": "Removes the policy. Conversations and tickets it applied to keep whatever coverage state they already reached; nothing is recomputed against a policy that no longer exists.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "SLAs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/triggers": {
      "get": {
        "summary": "List automation triggers",
        "description": "Every trigger in the workspace, enabled and disabled alike, with its `run_count` and `last_run_at`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Triggers"
        ],
        "responses": {
          "200": {
            "description": "Automation triggers",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d8d8d8d8-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "name": "Avisar si nadie responde en 2 horas",
                      "description": "Asigna al equipo de guardia cuando una conversación queda inactiva.",
                      "event": "conversation_idle",
                      "conditions": {
                        "logic": "all",
                        "items": [
                          {
                            "id": "c1",
                            "field": "channel",
                            "op": "eq",
                            "value": "whatsapp"
                          }
                        ]
                      },
                      "actions": [
                        {
                          "id": "a1",
                          "type": "assign_team",
                          "value": "cccccccc-0000-4000-8000-000000000001"
                        }
                      ],
                      "enabled": true,
                      "fire_once_per_conversation": true,
                      "stop_after_run": false,
                      "duration_hours": 2,
                      "run_count": 18,
                      "last_run_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-10T13:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create an automation trigger",
        "description": "Each condition’s `op` must fit its `field`’s type — an operator that does not belong to that field is a **400** naming it. `event: \"conversation_idle\"` requires `duration_hours` (1–720); every other event ignores it. `enabled` defaults to `true`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Triggers"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "conversation_created",
                      "message_from_customer",
                      "conversation_resolved",
                      "conversation_reopened",
                      "assignee_changed",
                      "tag_added",
                      "first_response_from_team",
                      "conversation_idle",
                      "sla_breached",
                      "business_hours_off",
                      "new_contact",
                      "lead_created",
                      "whatsapp_flow_completed",
                      "ticket_stage_changed"
                    ]
                  },
                  "conditions": {
                    "type": "object",
                    "properties": {
                      "logic": {
                        "type": "string",
                        "enum": [
                          "all",
                          "any"
                        ],
                        "default": "all"
                      },
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "field": {
                              "type": "string",
                              "enum": [
                                "conversation.channel",
                                "conversation.source",
                                "conversation.status",
                                "conversation.handler",
                                "conversation.assignee",
                                "conversation.team",
                                "conversation.tag",
                                "message.body",
                                "contact.country",
                                "contact.language",
                                "contact.lifecycle_stage",
                                "contact.first_seen_days",
                                "hours.is_business_hours",
                                "flow.key",
                                "flow.answers",
                                "flow.booking_status"
                              ]
                            },
                            "op": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 32
                            },
                            "value": {}
                          },
                          "required": [
                            "field",
                            "op"
                          ]
                        },
                        "maxItems": 25,
                        "default": []
                      }
                    }
                  },
                  "actions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "send_message",
                            "add_private_note",
                            "assign_agent",
                            "assign_team",
                            "move_to_stage",
                            "add_tag",
                            "change_status",
                            "snooze_until",
                            "run_webhook"
                          ]
                        },
                        "value": {}
                      },
                      "required": [
                        "type"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 15
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "fire_once_per_conversation": {
                    "type": "boolean"
                  },
                  "stop_after_run": {
                    "type": "boolean"
                  },
                  "duration_hours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 720
                  }
                },
                "required": [
                  "name",
                  "event"
                ]
              },
              "example": {
                "name": "Avisar si nadie responde en 2 horas",
                "event": "conversation_idle",
                "duration_hours": 2,
                "actions": [
                  {
                    "id": "a1",
                    "type": "assign_team",
                    "value": "cccccccc-0000-4000-8000-000000000001"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created trigger",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d8d8d8d8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Avisar si nadie responde en 2 horas",
                    "description": "Asigna al equipo de guardia cuando una conversación queda inactiva.",
                    "event": "conversation_idle",
                    "conditions": {
                      "logic": "all",
                      "items": [
                        {
                          "id": "c1",
                          "field": "channel",
                          "op": "eq",
                          "value": "whatsapp"
                        }
                      ]
                    },
                    "actions": [
                      {
                        "id": "a1",
                        "type": "assign_team",
                        "value": "cccccccc-0000-4000-8000-000000000001"
                      }
                    ],
                    "enabled": true,
                    "fire_once_per_conversation": true,
                    "stop_after_run": false,
                    "duration_hours": 2,
                    "run_count": 18,
                    "last_run_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/triggers/{id}": {
      "get": {
        "summary": "Fetch one automation trigger",
        "description": "The full trigger, including its `conditions` tree and ordered `actions` list, plus `run_count` and `last_run_at`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Triggers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The trigger",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d8d8d8d8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Avisar si nadie responde en 2 horas",
                    "description": "Asigna al equipo de guardia cuando una conversación queda inactiva.",
                    "event": "conversation_idle",
                    "conditions": {
                      "logic": "all",
                      "items": [
                        {
                          "id": "c1",
                          "field": "channel",
                          "op": "eq",
                          "value": "whatsapp"
                        }
                      ]
                    },
                    "actions": [
                      {
                        "id": "a1",
                        "type": "assign_team",
                        "value": "cccccccc-0000-4000-8000-000000000001"
                      }
                    ],
                    "enabled": true,
                    "fire_once_per_conversation": true,
                    "stop_after_run": false,
                    "duration_hours": 2,
                    "run_count": 18,
                    "last_run_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "put": {
        "summary": "Update an automation trigger",
        "description": "A PUT that behaves as a PATCH: only the fields present are applied and at least one is required. `conditions` and `actions` are each replaced wholesale when sent.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Triggers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "event": {
                    "type": "string",
                    "enum": [
                      "conversation_created",
                      "message_from_customer",
                      "conversation_resolved",
                      "conversation_reopened",
                      "assignee_changed",
                      "tag_added",
                      "first_response_from_team",
                      "conversation_idle",
                      "sla_breached",
                      "business_hours_off",
                      "new_contact",
                      "lead_created",
                      "whatsapp_flow_completed",
                      "ticket_stage_changed"
                    ]
                  },
                  "conditions": {
                    "type": "object",
                    "properties": {
                      "logic": {
                        "type": "string",
                        "enum": [
                          "all",
                          "any"
                        ],
                        "default": "all"
                      },
                      "items": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "string",
                              "maxLength": 64
                            },
                            "field": {
                              "type": "string",
                              "enum": [
                                "conversation.channel",
                                "conversation.source",
                                "conversation.status",
                                "conversation.handler",
                                "conversation.assignee",
                                "conversation.team",
                                "conversation.tag",
                                "message.body",
                                "contact.country",
                                "contact.language",
                                "contact.lifecycle_stage",
                                "contact.first_seen_days",
                                "hours.is_business_hours",
                                "flow.key",
                                "flow.answers",
                                "flow.booking_status"
                              ]
                            },
                            "op": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 32
                            },
                            "value": {}
                          },
                          "required": [
                            "field",
                            "op"
                          ]
                        },
                        "maxItems": 25,
                        "default": []
                      }
                    }
                  },
                  "actions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string",
                          "maxLength": 64
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "send_message",
                            "add_private_note",
                            "assign_agent",
                            "assign_team",
                            "move_to_stage",
                            "add_tag",
                            "change_status",
                            "snooze_until",
                            "run_webhook"
                          ]
                        },
                        "value": {}
                      },
                      "required": [
                        "type"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 15
                  },
                  "enabled": {
                    "type": "boolean"
                  },
                  "fire_once_per_conversation": {
                    "type": "boolean"
                  },
                  "stop_after_run": {
                    "type": "boolean"
                  },
                  "duration_hours": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 720
                  }
                }
              },
              "example": {
                "enabled": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated trigger",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d8d8d8d8-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "name": "Avisar si nadie responde en 2 horas",
                    "description": "Asigna al equipo de guardia cuando una conversación queda inactiva.",
                    "event": "conversation_idle",
                    "conditions": {
                      "logic": "all",
                      "items": [
                        {
                          "id": "c1",
                          "field": "channel",
                          "op": "eq",
                          "value": "whatsapp"
                        }
                      ]
                    },
                    "actions": [
                      {
                        "id": "a1",
                        "type": "assign_team",
                        "value": "cccccccc-0000-4000-8000-000000000001"
                      }
                    ],
                    "enabled": false,
                    "fire_once_per_conversation": true,
                    "stop_after_run": false,
                    "duration_hours": 2,
                    "run_count": 18,
                    "last_run_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-10T13:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete an automation trigger",
        "description": "Stops it from firing again; past runs are not undone.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Triggers"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/sending-domains": {
      "get": {
        "summary": "List sending domains",
        "description": "Every domain the workspace has registered, verified or not.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sending Domains"
        ],
        "responses": {
          "200": {
            "description": "Sending domains",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "d9d9d9d9-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "domain": "mail.autosdelvalle.cl",
                      "dkim_tokens": [
                        "ab12cd34ef56gh78",
                        "ij90kl12mn34op56",
                        "qr78st90uv12wx34"
                      ],
                      "dkim_status": "verified",
                      "mail_from_domain": "bounce.mail.autosdelvalle.cl",
                      "mail_from_status": "verified",
                      "verified_for_sending": true,
                      "tracking_domain": "click.mail.autosdelvalle.cl",
                      "tracking_ready": true,
                      "last_checked_at": "2026-09-15T18:20:00.000Z",
                      "created_by": "11111111-0000-4000-8000-000000000001",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-15T18:20:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Register a sending domain",
        "description": "Creates the SES identity and answers the stored row plus `records` — the DNS entries (DKIM CNAMEs, MAIL FROM MX + TXT) the workspace must publish at its registrar before anything can send from it. Every status field starts `pending`; call `POST /sending-domains/{id}/verify` once DNS has propagated.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sending Domains"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "domain": {
                    "type": "string",
                    "minLength": 4,
                    "maxLength": 253
                  }
                },
                "required": [
                  "domain"
                ]
              },
              "example": {
                "domain": "mail.autosdelvalle.cl"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The registered domain with its DNS checklist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "domain": "mail.autosdelvalle.cl",
                    "dkim_tokens": [
                      "ab12cd34ef56gh78",
                      "ij90kl12mn34op56",
                      "qr78st90uv12wx34"
                    ],
                    "dkim_status": "pending",
                    "mail_from_domain": "bounce.mail.autosdelvalle.cl",
                    "mail_from_status": "pending",
                    "verified_for_sending": false,
                    "tracking_domain": "click.mail.autosdelvalle.cl",
                    "tracking_ready": false,
                    "last_checked_at": null,
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z",
                    "records": [
                      {
                        "type": "CNAME",
                        "name": "ab12cd34ef56gh78._domainkey.mail.autosdelvalle.cl",
                        "value": "ab12cd34ef56gh78.dkim.amazonses.com"
                      },
                      {
                        "type": "MX",
                        "name": "bounce.mail.autosdelvalle.cl",
                        "value": "10 feedback-smtp.us-east-1.amazonses.com"
                      },
                      {
                        "type": "TXT",
                        "name": "bounce.mail.autosdelvalle.cl",
                        "value": "v=spf1 include:amazonses.com ~all"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/sending-domains/{id}/verify": {
      "post": {
        "summary": "Re-check a sending domain against SES and DNS",
        "description": "Re-polls SES for `dkim_status` / `mail_from_status` / `verified_for_sending` and re-resolves the tracking CNAME. Safe to call repeatedly while DNS is propagating; nothing here mutates the domain itself.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sending Domains"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The domain with its current verification state",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "d9d9d9d9-0000-4000-8000-000000000001",
                    "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                    "domain": "mail.autosdelvalle.cl",
                    "dkim_tokens": [
                      "ab12cd34ef56gh78",
                      "ij90kl12mn34op56",
                      "qr78st90uv12wx34"
                    ],
                    "dkim_status": "verified",
                    "mail_from_domain": "bounce.mail.autosdelvalle.cl",
                    "mail_from_status": "verified",
                    "verified_for_sending": true,
                    "tracking_domain": "click.mail.autosdelvalle.cl",
                    "tracking_ready": true,
                    "last_checked_at": "2026-09-15T18:20:00.000Z",
                    "created_by": "11111111-0000-4000-8000-000000000001",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/sending-domains/{id}": {
      "delete": {
        "summary": "Remove a sending domain",
        "description": "Removes the row; the SES identity itself is not deleted, since campaigns already sent still reference it for reporting.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sending Domains"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/sending-domains/quota": {
      "get": {
        "summary": "The account's SES send quota",
        "description": "Account-level, not per-domain: the 24-hour send cap, how much of it is already spent, the send rate, and whether the AWS account is in SES sandbox or production access. Surfaces the ceiling a campaign can hit regardless of which sending domain it uses.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Sending Domains"
        ],
        "responses": {
          "200": {
            "description": "The account send quota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "max_24_hour_send": 50000,
                    "sent_last_24_hours": 812,
                    "max_send_rate": 14,
                    "production_access_enabled": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/{id}/analytics": {
      "get": {
        "summary": "A WhatsApp template’s analytics (Vitrina’s own data)",
        "description": "What happened to every send of this template in the range, from Vitrina’s own records — available on every number, including the WhatsApp Business app (coexistence) numbers Meta gives no template insights. `sent` = sends Meta accepted (every sender: campaigns, Seguimientos, Recontacto, first touch, clinic notices, the inbox, tests); `delivered` / `read` / `failed` from the delivery receipts; `replied` = the person wrote on the same number within 72 h and this was their latest template; `opted_out` = the number opted out within 72 h. Broken down by sender, by quick-reply button (a reply whose first line is a button label) and by Meta failure code with a plain-language reason, plus a per-day (or per-week) series by send time in the workspace time zone. Sends are kept 400 days; sender and reply tracking start 2026-10-14 (older sends were backfilled where derivable).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Range start: an ISO instant, or a calendar date (YYYY-MM-DD) in the workspace time zone. Default: 30 days before `to`."
            },
            "required": false,
            "description": "Range start: an ISO instant, or a calendar date (YYYY-MM-DD) in the workspace time zone. Default: 30 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Range end: an ISO instant, or a calendar date (YYYY-MM-DD, inclusive — runs to the end of that day) in the workspace time zone. Default: now. At most 400 days after `from`."
            },
            "required": false,
            "description": "Range end: an ISO instant, or a calendar date (YYYY-MM-DD, inclusive — runs to the end of that day) in the workspace time zone. Default: now. At most 400 days after `from`.",
            "name": "to",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week"
              ],
              "description": "Bucket of the `daily` series. Default: day, or week for a range longer than 92 days."
            },
            "required": false,
            "description": "Bucket of the `daily` series. Default: day, or week for a range longer than 92 days.",
            "name": "group_by",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The template’s analytics",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappTemplateAnalytics"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "template": {
                      "id": "0199a3c2-7b10-7000-8000-0000000000a1",
                      "name": "invitacion_exclusiva_v2",
                      "language": "es",
                      "category": "MARKETING",
                      "status": "APPROVED",
                      "messaging_account_id": "0199a3c2-7b10-7000-8000-0000000000b1"
                    },
                    "range": {
                      "from": "2026-09-15T03:00:00.000Z",
                      "to": "2026-10-14T18:00:00.000Z",
                      "timezone": "America/Santiago",
                      "group_by": "day"
                    },
                    "totals": {
                      "sent": 120,
                      "delivered": 110,
                      "read": 84,
                      "failed": 6,
                      "replied": 31,
                      "opted_out": 2
                    },
                    "rates": {
                      "delivered": 0.9167,
                      "read": 0.7636,
                      "replied": 0.2583,
                      "failed": 0.05
                    },
                    "by_sender": [
                      {
                        "sender": "campaign",
                        "sent": 100,
                        "delivered": 92,
                        "read": 70,
                        "failed": 5,
                        "replied": 24
                      },
                      {
                        "sender": "manual",
                        "sent": 20,
                        "delivered": 18,
                        "read": 14,
                        "failed": 1,
                        "replied": 7
                      }
                    ],
                    "buttons": [
                      {
                        "text": "Quiero mi cupo",
                        "count": 18
                      },
                      {
                        "text": "No me interesa",
                        "count": 3
                      }
                    ],
                    "free_text": 10,
                    "failures": [
                      {
                        "code": "131026",
                        "title": "Message undeliverable",
                        "human_es": "El número no puede recibir mensajes de WhatsApp.",
                        "human_en": "The number cannot receive WhatsApp messages.",
                        "action": "remove_number",
                        "count": 6
                      }
                    ],
                    "daily": [
                      {
                        "date": "2026-10-13",
                        "sent": 80,
                        "delivered": 74,
                        "read": 60,
                        "failed": 4,
                        "replied": 22
                      },
                      {
                        "date": "2026-10-14",
                        "sent": 40,
                        "delivered": 36,
                        "read": 24,
                        "failed": 2,
                        "replied": 9
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/analytics/summary": {
      "get": {
        "summary": "Sends, reads, replies and failures per WhatsApp template",
        "description": "The list-page numbers for every template of the workspace in one read: per template with at least one send in the range, the counts of `GET /whatsapp-templates/{id}/analytics`’s totals. Same sources and definitions.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Range start: an ISO instant, or a calendar date (YYYY-MM-DD) in the workspace time zone. Default: 30 days before `to`."
            },
            "required": false,
            "description": "Range start: an ISO instant, or a calendar date (YYYY-MM-DD) in the workspace time zone. Default: 30 days before `to`.",
            "name": "from",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "Range end: an ISO instant, or a calendar date (YYYY-MM-DD, inclusive — runs to the end of that day) in the workspace time zone. Default: now. At most 400 days after `from`."
            },
            "required": false,
            "description": "Range end: an ISO instant, or a calendar date (YYYY-MM-DD, inclusive — runs to the end of that day) in the workspace time zone. Default: now. At most 400 days after `from`.",
            "name": "to",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Per-template counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappTemplateAnalyticsSummary"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "range": {
                      "from": "2026-09-15T03:00:00.000Z",
                      "to": "2026-10-14T18:00:00.000Z",
                      "timezone": "America/Santiago"
                    },
                    "templates": [
                      {
                        "template_id": "0199a3c2-7b10-7000-8000-0000000000a1",
                        "sent": 120,
                        "delivered": 110,
                        "read": 84,
                        "failed": 6,
                        "replied": 31,
                        "opted_out": 2
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates": {
      "get": {
        "summary": "List WhatsApp templates",
        "description": "Optionally narrowed to one `messaging_account_id` and/or `status`. Every template the workspace has created, mirrored from Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": false,
            "name": "messaging_account_id",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "APPROVED",
                "REJECTED",
                "IN_APPEAL",
                "PAUSED",
                "DISABLED"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "description": "true = only templates a person may hand-pick (campaign composer, inbox picker): drops platform-managed rows (`managed_for` set — waitlist offers, reserved notices, Recontacto, Seguimientos), which Vitrina sends itself for one event."
            },
            "required": false,
            "description": "true = only templates a person may hand-pick (campaign composer, inbox picker): drops platform-managed rows (`managed_for` set — waitlist offers, reserved notices, Recontacto, Seguimientos), which Vitrina sends itself for one event.",
            "name": "pickable",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "WhatsApp templates",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "e2e2e2e2-0000-4000-8000-000000000001",
                      "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                      "name": "seguimiento_stock",
                      "language": "es_CL",
                      "category": "MARKETING",
                      "submitted_category": "UTILITY",
                      "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                      "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                      "header_text": null,
                      "header_media_url": null,
                      "footer_text": "Autos del Valle",
                      "params": [
                        "1",
                        "2"
                      ],
                      "param_meta": {
                        "1": {
                          "label": "Nombre del contacto",
                          "example": "María"
                        },
                        "2": {
                          "label": "Modelo consultado",
                          "example": "Chevrolet Onix 2023"
                        }
                      },
                      "param_binding": {
                        "1": {
                          "source": "contact_field",
                          "field": "name"
                        },
                        "2": {
                          "source": "lead_interest"
                        }
                      },
                      "components": null,
                      "status": "APPROVED",
                      "rejection_reason": null,
                      "managed_for": null,
                      "managed_key": null,
                      "usage": "follow_up",
                      "synced_at": "2026-09-15T18:20:00.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-15T18:20:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Submit a WhatsApp template to Meta",
        "description": "`name` is lowercase letters, digits and underscores only (Meta’s constraint); `language` looks like `es`, `es_CL` or `en_US`. Answers 201 with `status: \"PENDING\"` — approval is asynchronous, reconciled by the worker poll or by `POST /whatsapp-templates/sync`.\n\nA MEDIA header: `header_media: { format: IMAGE|VIDEO|DOCUMENT, url }` with a public https `url` (upload the file with `POST /whatsapp-templates/header-media`). Mutually exclusive with `header_text` — 422. The url is Meta’s sample AND the template’s default `header_media_url`: every send that supplies no `header_media` of its own uses it. `param_spec.slots` then carries `{ key: \"header_media\", component: \"header\", media_format }`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 512,
                    "pattern": "^[a-z0-9_]+$"
                  },
                  "language": {
                    "type": "string",
                    "pattern": "^[a-z]{2}(_[A-Z]{2})?$"
                  },
                  "category": {
                    "type": "string",
                    "enum": [
                      "MARKETING",
                      "UTILITY",
                      "AUTHENTICATION"
                    ]
                  },
                  "body_text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "header_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "header_media": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "format": {
                        "type": "string",
                        "enum": [
                          "IMAGE",
                          "VIDEO",
                          "DOCUMENT"
                        ]
                      },
                      "url": {
                        "type": "string",
                        "maxLength": 2000,
                        "format": "uri"
                      }
                    },
                    "required": [
                      "format",
                      "url"
                    ],
                    "additionalProperties": false
                  },
                  "footer_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "buttons": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "QUICK_REPLY"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            }
                          },
                          "required": [
                            "type",
                            "text"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "URL"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "url": {
                              "type": "string",
                              "maxLength": 2000,
                              "format": "uri"
                            },
                            "example": {
                              "type": "string",
                              "maxLength": 2000,
                              "format": "uri"
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "url"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "PHONE_NUMBER"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "phone_number": {
                              "type": "string",
                              "minLength": 5,
                              "maxLength": 20
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "phone_number"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "FLOW"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "flow": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "flow_action": {
                              "type": "string",
                              "enum": [
                                "navigate",
                                "data_exchange"
                              ]
                            },
                            "navigate_screen": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 256
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "flow"
                          ]
                        }
                      ]
                    },
                    "maxItems": 10
                  },
                  "param_meta": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "example": {
                          "type": "string",
                          "maxLength": 200
                        }
                      }
                    }
                  },
                  "usage": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "pattern": "^[a-z0-9_]+$"
                  }
                },
                "required": [
                  "messaging_account_id",
                  "name",
                  "language",
                  "category",
                  "body_text"
                ]
              },
              "examples": {
                "text": {
                  "value": {
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "footer_text": "Autos del Valle"
                  }
                },
                "image_header": {
                  "value": {
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "promo_primavera",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "body_text": "Hola {{nombre}}, esta semana tenemos precios especiales. ¿Te cuento?",
                    "header_media": {
                      "format": "IMAGE",
                      "url": "https://api.vitrina.example/api/v1/public/marketplace-media/0b6f1a2c-6d0e-4a55-9c39-1f4b2b6a7e10/3f2a9d4e-8c1b-4f6a-9e2d-7b5c4a3e1f00.jpg"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The submitted template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "MARKETING",
                    "category_reclassified_at": null,
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "PENDING",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": null,
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/header-media": {
      "post": {
        "summary": "Upload a media-header file (image, video or PDF)",
        "description": "Multipart upload in a field named `file`: `image/jpeg` or `image/png` up to 5 MB, `video/mp4` up to 16 MB (normalised to a WhatsApp-safe profile), `application/pdf` up to 16 MB — Meta’s own send caps. Answers `{ url, mime_type, size_bytes, format }`; `url` is a PUBLIC https link (Meta fetches it anonymously at send time), so anything uploaded here is readable by anyone with the link. Use it as `header_media.url` when creating a template, as a template’s `header_media_url` (`PATCH /whatsapp-templates/{id}`), or as a campaign’s per-campaign override `template_params.header_media = { source: \"static\", value: <url> }`. 415 on another type, 413 over the cap.\n\n**Or a JSON body** (`application/json`) — what an MCP connector sends through `call_operation`, which cannot build multipart: `{ filename, content, content_encoding?, content_type? }` plus the operation's own form fields. `content` is UTF-8 text by default, or base64 when `content_encoding` is `\"base64\"` (images, PDF, audio, video). The same byte cap applies to the decoded bytes (413 above it); invalid base64 is a 400; the content type defaults from the filename extension.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {}
                }
              },
              "example": {
                "file": "(binary file part: image/jpeg, image/png, video/mp4 or application/pdf)"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "filename": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 255,
                    "description": "File name with extension; the content type defaults from it."
                  },
                  "content": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 35000000,
                    "description": "The file. UTF-8 text by default; base64 when `content_encoding` is \"base64\" (images, PDF, audio, video)."
                  },
                  "content_encoding": {
                    "type": "string",
                    "enum": [
                      "utf8",
                      "base64"
                    ],
                    "description": "`utf8` (default) for text, `base64` for binary files."
                  },
                  "content_type": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "MIME type. Defaults from the extension (.png → image/png, …)."
                  }
                },
                "required": [
                  "filename",
                  "content"
                ]
              },
              "example": {
                "filename": "promo-octubre.png",
                "content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJ…",
                "content_encoding": "base64"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The stored file’s public link",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "url": "https://api.vitrina.example/api/v1/public/marketplace-media/0b6f1a2c-6d0e-4a55-9c39-1f4b2b6a7e10/3f2a9d4e-8c1b-4f6a-9e2d-7b5c4a3e1f00.jpg",
                    "mime_type": "image/jpeg",
                    "size_bytes": 284113,
                    "format": "IMAGE"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/category-check": {
      "post": {
        "summary": "Predict whether Meta will reclassify a UTILITY draft as MARKETING",
        "description": "Jev-calibrated preview the template editor calls before submit (#2972). Meta silently reclassifies UTILITY templates as MARKETING at review time; this warns before that happens instead of after. `category` other than `UTILITY` always answers `applicable: false` — Meta’s reclassification only runs that one direction, so nothing is asked. Never changes what gets submitted and never writes to the Jev ledger (a preview is not an acted-on decision — the actual submission is what gets ledgered and later graded against Meta’s verdict). `cause` is a deterministic guess (never Jev’s own answer) set only when `likely_marketing` is true.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "category": {
                    "type": "string",
                    "enum": [
                      "MARKETING",
                      "UTILITY",
                      "AUTHENTICATION"
                    ]
                  },
                  "body_text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024
                  },
                  "header_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "footer_text": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "buttons": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "QUICK_REPLY"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            }
                          },
                          "required": [
                            "type",
                            "text"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "URL"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "url": {
                              "type": "string",
                              "maxLength": 2000,
                              "format": "uri"
                            },
                            "example": {
                              "type": "string",
                              "maxLength": 2000,
                              "format": "uri"
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "url"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "PHONE_NUMBER"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "phone_number": {
                              "type": "string",
                              "minLength": 5,
                              "maxLength": 20
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "phone_number"
                          ]
                        },
                        {
                          "type": "object",
                          "properties": {
                            "type": {
                              "type": "string",
                              "enum": [
                                "FLOW"
                              ]
                            },
                            "text": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 25
                            },
                            "flow": {
                              "type": "string",
                              "format": "uuid"
                            },
                            "flow_action": {
                              "type": "string",
                              "enum": [
                                "navigate",
                                "data_exchange"
                              ]
                            },
                            "navigate_screen": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 256
                            }
                          },
                          "required": [
                            "type",
                            "text",
                            "flow"
                          ]
                        }
                      ]
                    },
                    "maxItems": 10
                  }
                },
                "required": [
                  "category",
                  "body_text"
                ]
              },
              "example": {
                "category": "UTILITY",
                "body_text": "Hola {{1}}, ¿sigues interesado en el {{2}}? Si quieres, coordinamos una visita para que lo veas.",
                "footer_text": "Autos del Valle"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The prediction",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "applicable": true,
                    "likely_marketing": true,
                    "cause": "promotional_wording"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/{id}/param-meta": {
      "patch": {
        "summary": "Set the agent-facing label/example for each {{variable}}",
        "description": "Local-only — no Meta round-trip, no re-approval. Purely descriptive metadata for the composer (Seguimientos).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "param_meta": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "label": {
                          "type": "string",
                          "maxLength": 200
                        },
                        "example": {
                          "type": "string",
                          "maxLength": 200
                        }
                      }
                    }
                  }
                },
                "required": [
                  "param_meta"
                ]
              },
              "example": {
                "param_meta": {
                  "1": {
                    "label": "Nombre del contacto",
                    "example": "María"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/{id}/usage": {
      "patch": {
        "summary": "Re-file the template under another category",
        "description": "What the template is FOR, operator-facing — distinct from Meta's billing `category`. `usage` is the KEY of one of the workspace’s template categories (`GET /whatsapp-templates/categories`); an unknown key answers 422. Inferred from the name at create/sync, editable here. Local-only: no Meta round-trip, no re-approval. Templates in a category with `hidden_from_customers` are hidden from customer-facing send pickers.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "usage": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "pattern": "^[a-z0-9_]+$"
                  }
                },
                "required": [
                  "usage"
                ]
              },
              "example": {
                "usage": "follow_up"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/usage": {
      "patch": {
        "summary": "Re-file several templates under one category",
        "description": "Bulk form of `PATCH /whatsapp-templates/{id}/usage`: 1–200 template ids, one category key (422 when it is not one of the workspace’s). Ids that are not the workspace’s are ignored; `updated` counts the rows that actually changed. Local-only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  },
                  "usage": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 40,
                    "pattern": "^[a-z0-9_]+$"
                  }
                },
                "required": [
                  "ids",
                  "usage"
                ]
              },
              "example": {
                "ids": [
                  "e2e2e2e2-0000-4000-8000-000000000001"
                ],
                "usage": "follow_up"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many templates were re-filed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "updated": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/categories": {
      "get": {
        "summary": "List the workspace’s template categories",
        "description": "The operator-facing buckets templates are filed under (a template’s `usage` is one of these `key`s), ordered by `position`, each with the number of the workspace’s templates filed there. Every workspace starts with seven seeded (`system: true`) categories: first_contact, reminder, follow_up, update, aftercare, internal (hidden from customers) and other.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "responses": {
          "200": {
            "description": "Template categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c000",
                      "key": "first_contact",
                      "name": "Primer contacto",
                      "position": 0,
                      "hidden_from_customers": false,
                      "system": true,
                      "template_count": 2
                    },
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c001",
                      "key": "reminder",
                      "name": "Recordatorios",
                      "position": 1,
                      "hidden_from_customers": false,
                      "system": true,
                      "template_count": 4
                    },
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c005",
                      "key": "internal",
                      "name": "Equipo",
                      "position": 5,
                      "hidden_from_customers": true,
                      "system": true,
                      "template_count": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Add a template category",
        "description": "Appended at the end of the list. The immutable `key` is derived from `name` (deaccented, `[a-z0-9_]`, ≤40 chars) with a numeric suffix when taken.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "hidden_from_customers": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Cobranza",
                "hidden_from_customers": false
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The new category",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "data": {
                    "id": "0199a3c2-7b10-7000-8000-00000000c007",
                    "key": "cobranza",
                    "name": "Cobranza",
                    "position": 7,
                    "hidden_from_customers": false,
                    "system": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/categories/order": {
      "put": {
        "summary": "Reorder the template categories",
        "description": "`ids` must list every category of the workspace exactly once (422 otherwise); positions are rewritten to the array order. Answers the reordered list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 200
                  }
                },
                "required": [
                  "ids"
                ]
              },
              "example": {
                "ids": [
                  "0199a3c2-7b10-7000-8000-00000000c001",
                  "0199a3c2-7b10-7000-8000-00000000c000",
                  "0199a3c2-7b10-7000-8000-00000000c005"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Template categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c000",
                      "key": "first_contact",
                      "name": "Primer contacto",
                      "position": 0,
                      "hidden_from_customers": false,
                      "system": true,
                      "template_count": 2
                    },
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c001",
                      "key": "reminder",
                      "name": "Recordatorios",
                      "position": 1,
                      "hidden_from_customers": false,
                      "system": true,
                      "template_count": 4
                    },
                    {
                      "id": "0199a3c2-7b10-7000-8000-00000000c005",
                      "key": "internal",
                      "name": "Equipo",
                      "position": 5,
                      "hidden_from_customers": true,
                      "system": true,
                      "template_count": 1
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/categories/{id}": {
      "delete": {
        "summary": "Delete a template category",
        "description": "Its templates are re-filed under `other` in the same transaction. `internal` and `other` cannot be deleted (400).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Rename a template category or change its visibility",
        "description": "Seeded categories can be renamed and hidden too; the `internal` category must stay hidden from customers (400 when unhiding it). The `key` never changes.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "hidden_from_customers": {
                    "type": "boolean"
                  }
                }
              },
              "example": {
                "name": "Recordatorios de cita"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated category",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "0199a3c2-7b10-7000-8000-00000000c001",
                    "key": "reminder",
                    "name": "Recordatorios de cita",
                    "position": 1,
                    "hidden_from_customers": false,
                    "system": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/{id}/param-binding": {
      "patch": {
        "summary": "Set what fills each {{variable}} when Vitrina sends this template",
        "description": "Local-only, like `param-meta`: describes how Vitrina fills an ALREADY-APPROVED body, so there is no Meta round-trip and no re-approval. Unknown slot keys and malformed entries are dropped rather than failing the whole request.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "param_binding": {
                    "type": "object",
                    "additionalProperties": {
                      "type": "object",
                      "properties": {
                        "source": {
                          "type": "string",
                          "enum": [
                            "contact_field",
                            "contact_attribute",
                            "lead_interest",
                            "conversation_origin",
                            "business",
                            "static",
                            "ask"
                          ]
                        },
                        "field": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        },
                        "value": {
                          "type": "string",
                          "maxLength": 500
                        }
                      },
                      "required": [
                        "source"
                      ]
                    }
                  }
                },
                "required": [
                  "param_binding"
                ]
              },
              "example": {
                "param_binding": {
                  "1": {
                    "source": "contact_field",
                    "field": "name"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/{id}/infer-bindings": {
      "post": {
        "summary": "Re-run the binder over every slot (\"volver a sugerir\")",
        "description": "Suggests a binding for every `{{variable}}`, not just the unbound ones. The merge rule never overwrites a slot a person already bound by hand (`origin: \"manual\"`), so re-running this can never lose the workspace’s own choices.\n\n**Connected apps:** refused with `403 CONNECTED_APP_EXCLUDED_OPERATION` and absent from the MCP catalogue, whatever write pack was granted — the «Agentes de IA» pack promises not to include this act. An API key or a personal token of the workspace holding the scope reaches it as usual.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The template with its inferred bindings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-connector-excluded": true
      }
    },
    "/whatsapp-templates/readiness": {
      "get": {
        "summary": "Are the workspace’s WhatsApp templates ready?",
        "description": "Per ENABLED feature (first touch, and each feature as it joins the pack registry) and per connected number: the templates it needs, with counts of `required`, `approved`, `pending`, `rejected`, `missing` and `blocked` (paused or disabled by Meta), and the rejection reason where there is one. A feature that is off answers `status: \"not_needed\"` with a `reason` and is left out of the totals. Reads only: nothing is sent to Meta. `ready` is true when every required template is approved. Use `POST /whatsapp-templates/readiness/retry` to resubmit.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "A feature pack id from the readiness answer, e.g. `first_touch`."
            },
            "required": false,
            "description": "A feature pack id from the readiness answer, e.g. `first_touch`.",
            "name": "pack",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid",
              "description": "Only this WhatsApp number."
            },
            "required": false,
            "description": "Only this WhatsApp number.",
            "name": "messaging_account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The readiness report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "ready": {
                          "type": "boolean"
                        },
                        "counts": {
                          "type": "object",
                          "properties": {
                            "required": {
                              "type": "integer"
                            },
                            "approved": {
                              "type": "integer"
                            },
                            "pending": {
                              "type": "integer"
                            },
                            "rejected": {
                              "type": "integer"
                            },
                            "missing": {
                              "type": "integer"
                            },
                            "blocked": {
                              "type": "integer"
                            }
                          },
                          "required": [
                            "required",
                            "approved",
                            "pending",
                            "rejected",
                            "missing",
                            "blocked"
                          ]
                        },
                        "packs": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "pack": {
                                "type": "string",
                                "minLength": 1,
                                "description": "A feature pack id from the readiness answer, e.g. `first_touch`."
                              },
                              "feature": {
                                "type": "string"
                              },
                              "audience": {
                                "type": "string",
                                "enum": [
                                  "customer",
                                  "staff"
                                ]
                              },
                              "status": {
                                "type": "string",
                                "enum": [
                                  "enabled",
                                  "not_needed",
                                  "unavailable"
                                ]
                              },
                              "reason": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "ready": {
                                "type": "boolean"
                              },
                              "counts": {
                                "type": "object",
                                "properties": {
                                  "required": {
                                    "type": "integer"
                                  },
                                  "approved": {
                                    "type": "integer"
                                  },
                                  "pending": {
                                    "type": "integer"
                                  },
                                  "rejected": {
                                    "type": "integer"
                                  },
                                  "missing": {
                                    "type": "integer"
                                  },
                                  "blocked": {
                                    "type": "integer"
                                  }
                                },
                                "required": [
                                  "required",
                                  "approved",
                                  "pending",
                                  "rejected",
                                  "missing",
                                  "blocked"
                                ]
                              },
                              "numbers": {
                                "type": "array",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "messaging_account_id": {
                                      "type": "string",
                                      "format": "uuid"
                                    },
                                    "counts": {
                                      "type": "object",
                                      "properties": {
                                        "required": {
                                          "type": "integer"
                                        },
                                        "approved": {
                                          "type": "integer"
                                        },
                                        "pending": {
                                          "type": "integer"
                                        },
                                        "rejected": {
                                          "type": "integer"
                                        },
                                        "missing": {
                                          "type": "integer"
                                        },
                                        "blocked": {
                                          "type": "integer"
                                        }
                                      },
                                      "required": [
                                        "required",
                                        "approved",
                                        "pending",
                                        "rejected",
                                        "missing",
                                        "blocked"
                                      ]
                                    },
                                    "templates": {
                                      "type": "array",
                                      "items": {
                                        "type": "object",
                                        "properties": {
                                          "entry": {
                                            "type": "string"
                                          },
                                          "name": {
                                            "type": "string"
                                          },
                                          "language": {
                                            "type": "string"
                                          },
                                          "state": {
                                            "type": "string",
                                            "enum": [
                                              "missing",
                                              "pending",
                                              "approved",
                                              "rejected",
                                              "blocked"
                                            ]
                                          },
                                          "status": {
                                            "type": [
                                              "string",
                                              "null"
                                            ],
                                            "enum": [
                                              "PENDING",
                                              "APPROVED",
                                              "REJECTED",
                                              "IN_APPEAL",
                                              "PAUSED",
                                              "DISABLED"
                                            ]
                                          },
                                          "rejection_reason": {
                                            "type": [
                                              "string",
                                              "null"
                                            ]
                                          },
                                          "category": {
                                            "type": [
                                              "string",
                                              "null"
                                            ],
                                            "enum": [
                                              "MARKETING",
                                              "UTILITY",
                                              "AUTHENTICATION"
                                            ],
                                            "description": "The category Meta finally assigned; null while missing."
                                          },
                                          "category_reclassified": {
                                            "type": "boolean",
                                            "description": "Meta changed the category after submission (for example UTILITY to MARKETING)."
                                          },
                                          "error": {
                                            "type": [
                                              "string",
                                              "null"
                                            ]
                                          }
                                        },
                                        "required": [
                                          "entry",
                                          "name",
                                          "language",
                                          "state",
                                          "status",
                                          "rejection_reason",
                                          "category",
                                          "category_reclassified",
                                          "error"
                                        ]
                                      }
                                    }
                                  },
                                  "required": [
                                    "messaging_account_id",
                                    "counts",
                                    "templates"
                                  ]
                                }
                              }
                            },
                            "required": [
                              "pack",
                              "feature",
                              "audience",
                              "status",
                              "reason",
                              "ready",
                              "counts",
                              "numbers"
                            ]
                          }
                        }
                      },
                      "required": [
                        "ready",
                        "counts",
                        "packs"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "ready": false,
                    "counts": {
                      "required": 2,
                      "approved": 1,
                      "pending": 0,
                      "rejected": 1,
                      "missing": 0,
                      "blocked": 0
                    },
                    "packs": [
                      {
                        "pack": "first_touch",
                        "feature": "First reply to a new customer",
                        "audience": "customer",
                        "status": "enabled",
                        "reason": null,
                        "ready": false,
                        "counts": {
                          "required": 2,
                          "approved": 1,
                          "pending": 0,
                          "rejected": 1,
                          "missing": 0,
                          "blocked": 0
                        },
                        "numbers": [
                          {
                            "messaging_account_id": "0199a3c2-7b10-7000-8000-0000000000a1",
                            "counts": {
                              "required": 2,
                              "approved": 1,
                              "pending": 0,
                              "rejected": 1,
                              "missing": 0,
                              "blocked": 0
                            },
                            "templates": [
                              {
                                "entry": "website_named",
                                "name": "first_touch_website_named",
                                "language": "es",
                                "state": "approved",
                                "status": "APPROVED",
                                "rejection_reason": null,
                                "error": null
                              },
                              {
                                "entry": "portal_named",
                                "name": "first_touch_portal_named",
                                "language": "es",
                                "state": "rejected",
                                "status": "REJECTED",
                                "rejection_reason": "INVALID_FORMAT",
                                "error": null
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-templates/readiness/retry": {
      "post": {
        "summary": "Resubmit missing or rejected feature templates",
        "description": "Resubmits through the template pack: a MISSING template is submitted as is; a REJECTED one goes to Meta under a new versioned name (`<name>_r2`, `_r3`, …) because Meta refuses to reuse a rejected name. Approved and pending templates are left alone. Answers what was done per template plus the pack’s readiness afterwards (submitted ones are `pending` until Meta decides).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "pack": {
                    "type": "string",
                    "minLength": 1,
                    "description": "A feature pack id from the readiness answer, e.g. `first_touch`."
                  },
                  "entry": {
                    "type": "string",
                    "minLength": 1,
                    "description": "One template of the pack (its `entry` key from the readiness answer). Omit to retry every missing or rejected template of the pack."
                  },
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Only this WhatsApp number. Omit for every number."
                  }
                },
                "required": [
                  "pack"
                ]
              },
              "example": {
                "pack": "first_touch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was resubmitted",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "results": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "entry": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "language": {
                                "type": "string"
                              },
                              "state": {
                                "type": "string",
                                "enum": [
                                  "missing",
                                  "pending",
                                  "approved",
                                  "rejected",
                                  "blocked"
                                ]
                              },
                              "status": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "PENDING",
                                  "APPROVED",
                                  "REJECTED",
                                  "IN_APPEAL",
                                  "PAUSED",
                                  "DISABLED"
                                ]
                              },
                              "rejection_reason": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "category": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "MARKETING",
                                  "UTILITY",
                                  "AUTHENTICATION"
                                ],
                                "description": "The category Meta finally assigned; null while missing."
                              },
                              "category_reclassified": {
                                "type": "boolean",
                                "description": "Meta changed the category after submission (for example UTILITY to MARKETING)."
                              },
                              "error": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "pack": {
                                "type": "string",
                                "minLength": 1,
                                "description": "A feature pack id from the readiness answer, e.g. `first_touch`."
                              },
                              "messaging_account_id": {
                                "type": "string",
                                "format": "uuid"
                              },
                              "action": {
                                "type": "string",
                                "enum": [
                                  "submitted",
                                  "exists",
                                  "failed",
                                  "would_submit"
                                ]
                              }
                            },
                            "required": [
                              "entry",
                              "name",
                              "language",
                              "state",
                              "status",
                              "rejection_reason",
                              "category",
                              "category_reclassified",
                              "error",
                              "pack",
                              "messaging_account_id",
                              "action"
                            ]
                          }
                        },
                        "readiness": {
                          "type": "object",
                          "properties": {
                            "pack": {
                              "type": "string",
                              "minLength": 1,
                              "description": "A feature pack id from the readiness answer, e.g. `first_touch`."
                            },
                            "feature": {
                              "type": "string"
                            },
                            "audience": {
                              "type": "string",
                              "enum": [
                                "customer",
                                "staff"
                              ]
                            },
                            "status": {
                              "type": "string",
                              "enum": [
                                "enabled",
                                "not_needed",
                                "unavailable"
                              ]
                            },
                            "reason": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "ready": {
                              "type": "boolean"
                            },
                            "counts": {
                              "type": "object",
                              "properties": {
                                "required": {
                                  "type": "integer"
                                },
                                "approved": {
                                  "type": "integer"
                                },
                                "pending": {
                                  "type": "integer"
                                },
                                "rejected": {
                                  "type": "integer"
                                },
                                "missing": {
                                  "type": "integer"
                                },
                                "blocked": {
                                  "type": "integer"
                                }
                              },
                              "required": [
                                "required",
                                "approved",
                                "pending",
                                "rejected",
                                "missing",
                                "blocked"
                              ]
                            },
                            "numbers": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "messaging_account_id": {
                                    "type": "string",
                                    "format": "uuid"
                                  },
                                  "counts": {
                                    "type": "object",
                                    "properties": {
                                      "required": {
                                        "type": "integer"
                                      },
                                      "approved": {
                                        "type": "integer"
                                      },
                                      "pending": {
                                        "type": "integer"
                                      },
                                      "rejected": {
                                        "type": "integer"
                                      },
                                      "missing": {
                                        "type": "integer"
                                      },
                                      "blocked": {
                                        "type": "integer"
                                      }
                                    },
                                    "required": [
                                      "required",
                                      "approved",
                                      "pending",
                                      "rejected",
                                      "missing",
                                      "blocked"
                                    ]
                                  },
                                  "templates": {
                                    "type": "array",
                                    "items": {
                                      "type": "object",
                                      "properties": {
                                        "entry": {
                                          "type": "string"
                                        },
                                        "name": {
                                          "type": "string"
                                        },
                                        "language": {
                                          "type": "string"
                                        },
                                        "state": {
                                          "type": "string",
                                          "enum": [
                                            "missing",
                                            "pending",
                                            "approved",
                                            "rejected",
                                            "blocked"
                                          ]
                                        },
                                        "status": {
                                          "type": [
                                            "string",
                                            "null"
                                          ],
                                          "enum": [
                                            "PENDING",
                                            "APPROVED",
                                            "REJECTED",
                                            "IN_APPEAL",
                                            "PAUSED",
                                            "DISABLED"
                                          ]
                                        },
                                        "rejection_reason": {
                                          "type": [
                                            "string",
                                            "null"
                                          ]
                                        },
                                        "category": {
                                          "type": [
                                            "string",
                                            "null"
                                          ],
                                          "enum": [
                                            "MARKETING",
                                            "UTILITY",
                                            "AUTHENTICATION"
                                          ],
                                          "description": "The category Meta finally assigned; null while missing."
                                        },
                                        "category_reclassified": {
                                          "type": "boolean",
                                          "description": "Meta changed the category after submission (for example UTILITY to MARKETING)."
                                        },
                                        "error": {
                                          "type": [
                                            "string",
                                            "null"
                                          ]
                                        }
                                      },
                                      "required": [
                                        "entry",
                                        "name",
                                        "language",
                                        "state",
                                        "status",
                                        "rejection_reason",
                                        "category",
                                        "category_reclassified",
                                        "error"
                                      ]
                                    }
                                  }
                                },
                                "required": [
                                  "messaging_account_id",
                                  "counts",
                                  "templates"
                                ]
                              }
                            }
                          },
                          "required": [
                            "pack",
                            "feature",
                            "audience",
                            "status",
                            "reason",
                            "ready",
                            "counts",
                            "numbers"
                          ]
                        }
                      },
                      "required": [
                        "results",
                        "readiness"
                      ]
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "results": [
                      {
                        "pack": "first_touch",
                        "entry": "portal_named",
                        "messaging_account_id": "0199a3c2-7b10-7000-8000-0000000000a1",
                        "name": "first_touch_portal_named_r2",
                        "language": "es",
                        "action": "submitted",
                        "state": "pending",
                        "status": "PENDING",
                        "rejection_reason": null,
                        "error": null
                      }
                    ],
                    "readiness": {
                      "pack": "first_touch",
                      "feature": "First reply to a new customer",
                      "audience": "customer",
                      "status": "enabled",
                      "reason": null,
                      "ready": false,
                      "counts": {
                        "required": 2,
                        "approved": 1,
                        "pending": 0,
                        "rejected": 1,
                        "missing": 0,
                        "blocked": 0
                      },
                      "numbers": [
                        {
                          "messaging_account_id": "0199a3c2-7b10-7000-8000-0000000000a1",
                          "counts": {
                            "required": 2,
                            "approved": 1,
                            "pending": 0,
                            "rejected": 1,
                            "missing": 0,
                            "blocked": 0
                          },
                          "templates": [
                            {
                              "entry": "website_named",
                              "name": "first_touch_website_named",
                              "language": "es",
                              "state": "approved",
                              "status": "APPROVED",
                              "rejection_reason": null,
                              "error": null
                            },
                            {
                              "entry": "portal_named",
                              "name": "first_touch_portal_named",
                              "language": "es",
                              "state": "rejected",
                              "status": "REJECTED",
                              "rejection_reason": "INVALID_FORMAT",
                              "error": null
                            }
                          ]
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/sync": {
      "post": {
        "summary": "Force a reconcile of a channel's templates against Meta",
        "description": "Fetches every template Meta reports for the messaging account and updates local `status`/`rejection_reason`/`components`. Use this right after submitting a change on Meta’s side instead of waiting for the periodic poll.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "messaging_account_id"
                ]
              },
              "example": {
                "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reconcile counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "checked": 4,
                    "updated": 1,
                    "created": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-templates/{id}": {
      "get": {
        "summary": "Fetch one WhatsApp template",
        "description": "The local mirror row, including Meta’s `status`, the param labels/examples set on it, and its bindings.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The template",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "seguimiento_stock",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{1}}, el {{2}} que consultaste sigue disponible. ¿Seguimos viéndolo?",
                    "header_text": null,
                    "header_media_url": null,
                    "footer_text": "Autos del Valle",
                    "params": [
                      "1",
                      "2"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": null,
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a WhatsApp template",
        "description": "Removes the local mirror; does not withdraw it from Meta.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a WhatsApp template’s local settings",
        "description": "`header_media_url`: the DEFAULT public https link for a media (IMAGE / VIDEO / DOCUMENT) header — what fills the `header_media` slot when a sender supplies none; `null` clears it. The way a template authored in Meta Business Manager (whose synced sample is Meta’s own CDN handle, not a link anyone may send) becomes sendable by campaigns without an override and by the automated rails. Local only: no Meta round-trip, no re-approval, never overwritten by a sync. 422 on a template without a media header or a non-https url.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Templates"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "header_media_url": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000,
                    "format": "uri"
                  }
                },
                "required": [
                  "header_media_url"
                ],
                "additionalProperties": false
              },
              "example": {
                "header_media_url": "https://api.vitrina.example/api/v1/public/marketplace-media/0b6f1a2c-6d0e-4a55-9c39-1f4b2b6a7e10/3f2a9d4e-8c1b-4f6a-9e2d-7b5c4a3e1f00.jpg"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The template with its new default",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e2e2e2e2-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "name": "promo_primavera",
                    "language": "es_CL",
                    "category": "MARKETING",
                    "submitted_category": "UTILITY",
                    "category_reclassified_at": "2026-09-16T09:00:00.000Z",
                    "body_text": "Hola {{nombre}}, esta semana tenemos precios especiales. ¿Te cuento?",
                    "header_text": null,
                    "header_media_url": "https://api.vitrina.example/api/v1/public/marketplace-media/0b6f1a2c-6d0e-4a55-9c39-1f4b2b6a7e10/3f2a9d4e-8c1b-4f6a-9e2d-7b5c4a3e1f00.jpg",
                    "footer_text": "Autos del Valle",
                    "params": [
                      "nombre"
                    ],
                    "param_meta": {
                      "1": {
                        "label": "Nombre del contacto",
                        "example": "María"
                      },
                      "2": {
                        "label": "Modelo consultado",
                        "example": "Chevrolet Onix 2023"
                      }
                    },
                    "param_binding": {
                      "1": {
                        "source": "contact_field",
                        "field": "name"
                      },
                      "2": {
                        "source": "lead_interest"
                      }
                    },
                    "components": [
                      {
                        "type": "HEADER",
                        "format": "IMAGE",
                        "example": {
                          "header_handle": [
                            "https://scontent.whatsapp.net/v/t61.29466-34/sample.jpg"
                          ]
                        }
                      },
                      {
                        "type": "BODY",
                        "text": "Hola {{nombre}}, esta semana tenemos precios especiales. ¿Te cuento?"
                      }
                    ],
                    "status": "APPROVED",
                    "rejection_reason": null,
                    "managed_for": null,
                    "managed_key": null,
                    "usage": "follow_up",
                    "synced_at": "2026-09-15T18:20:00.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z",
                    "param_spec": {
                      "slots": [
                        {
                          "key": "header_media",
                          "component": "header",
                          "media_format": "image"
                        },
                        {
                          "key": "nombre",
                          "component": "body"
                        }
                      ],
                      "unsupported": [],
                      "supported": true,
                      "header_media_url": "https://api.vitrina.example/api/v1/public/marketplace-media/0b6f1a2c-6d0e-4a55-9c39-1f4b2b6a7e10/3f2a9d4e-8c1b-4f6a-9e2d-7b5c4a3e1f00.jpg"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows": {
      "get": {
        "summary": "Live list of a channel's WhatsApp Flows (composer picker)",
        "description": "A live proxy read from Meta via the bridge — includes a flow authored outside Vitrina that has not been synced into the builder yet. Each row also carries the agent settings of its local copy (`managed_id`, `key`, `description`, `ai_enabled` and the default `message_*`), all `null` when there is none. `messaging_account_id` is required; there is no tenant-wide list.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "messaging_account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "WhatsApp Flows reported by Meta",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "1200300400500600",
                      "name": "Agenda tu visita",
                      "status": "PUBLISHED",
                      "categories": [
                        "APPOINTMENT_BOOKING"
                      ],
                      "managed_id": "e3e3e3e3-0000-4000-8000-000000000001",
                      "key": "agenda_tu_visita",
                      "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                      "ai_enabled": true,
                      "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                      "message_cta": "Agendar visita",
                      "message_header": null,
                      "message_footer": null
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed": {
      "get": {
        "summary": "List flows built in the Vitrina flow builder",
        "description": "`messaging_account_id` is required. **Health** — pushed by Meta’s `flows` webhook: `health_status` is `blocked` / `throttled` (Meta stopped delivering the flow — sends fail with 132068 / 132069), `degraded` (an endpoint alert is open: error rate, p90 latency, availability, or the Flow JSON version is about to freeze) or `healthy`. `active_alerts` holds the open alerts keyed `error_rate:endpoint` / `error_rate:client` / `latency` / `availability` / `version_expiry` (plus `status`, carrying Meta’s reason, while throttled or blocked); each alert is `{ type, source, state, value, threshold, window, meta_event, message, details, occurred_at }`. `last_alert` is the most recent health event of any kind, recoveries included; `health_updated_at` when it was applied.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "messaging_account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Managed flows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WhatsappManagedFlow"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "id": "e3e3e3e3-0000-4000-8000-000000000001",
                      "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                      "meta_flow_id": "1200300400500600",
                      "name": "Agenda tu visita",
                      "categories": [
                        "APPOINTMENT_BOOKING"
                      ],
                      "status": "PUBLISHED",
                      "authoring": "builder",
                      "builder_state": {
                        "screens": [
                          {
                            "title": "¿Cuándo te acomoda?",
                            "footer_label": "Continuar",
                            "components": [
                              {
                                "kind": "heading",
                                "text": "Agenda tu visita"
                              },
                              {
                                "kind": "date_picker",
                                "name": "fecha",
                                "label": "Fecha preferida",
                                "required": true
                              }
                            ]
                          }
                        ]
                      },
                      "flow_json": null,
                      "validation_errors": null,
                      "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                      "preview_expires_at": "2026-09-15T19:20:00.000Z",
                      "key": "agenda_tu_visita",
                      "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                      "ai_enabled": true,
                      "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                      "message_cta": "Agendar visita",
                      "message_header": null,
                      "message_footer": null,
                      "completion_mode": "agent",
                      "field_map": {},
                      "confirmation_message": null,
                      "health_status": "degraded",
                      "active_alerts": {
                        "latency": {
                          "type": "latency",
                          "source": null,
                          "state": "activated",
                          "value": 8000,
                          "threshold": 7000,
                          "window": "30m",
                          "meta_event": "ENDPOINT_LATENCY",
                          "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                          "details": {
                            "p50_latency": 500,
                            "requests_count": 34
                          },
                          "occurred_at": "2026-09-15T18:10:00.000Z"
                        }
                      },
                      "last_alert": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      },
                      "health_updated_at": "2026-09-15T18:10:02.000Z",
                      "created_at": "2026-01-10T13:00:00.000Z",
                      "updated_at": "2026-09-15T18:20:00.000Z"
                    }
                  ],
                  "meta": {
                    "total": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a flow in the builder",
        "description": "`builder_state` is the ordered screen list; the Flow JSON is generated and uploaded to Meta as part of this call, and `validation_errors` reports what Meta rejected, if anything. Always created `DRAFT`.\n\n**Authoring** — send exactly one of `builder_state` (the builder: ordered screens of palette components, Flow JSON generated by Vitrina; see `GET /whatsapp-flows/capabilities` for the palette, data sources and limits of this workspace) or `flow_json` (advanced: a raw Meta Flow JSON, string or object, uploaded as-is — Meta is the validator and its findings come back as `validation_errors`; the flow is `authoring: \"json\"` from then on). A raw Flow JSON that declares `data_api_version` gets the workspace data endpoint, but Vitrina's data route only serves builder flows for now — such a flow answers its customers with a clear error instead of data.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "SIGN_UP",
                        "SIGN_IN",
                        "APPOINTMENT_BOOKING",
                        "LEAD_GENERATION",
                        "CONTACT_US",
                        "CUSTOMER_SUPPORT",
                        "SURVEY",
                        "OTHER"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 3
                  },
                  "builder_state": {
                    "type": "object",
                    "properties": {
                      "screens": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "footer_label": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 35
                            },
                            "footer_captions": {
                              "type": "object",
                              "properties": {
                                "left": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "center": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "right": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                }
                              },
                              "additionalProperties": false
                            },
                            "next_rules": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "when": {
                                    "$ref": "#/components/schemas/WhatsappFlowCondition"
                                  },
                                  "goto": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "maximum": 9
                                  }
                                },
                                "required": [
                                  "when",
                                  "goto"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 20
                            },
                            "default_next": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 9
                            },
                            "components": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/WhatsappFlowComponent"
                              },
                              "maxItems": 49
                            }
                          },
                          "required": [
                            "title",
                            "components"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 10
                      },
                      "summary_screen": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "screens"
                    ]
                  },
                  "flow_json": {
                    "$ref": "#/components/schemas/WhatsappFlowJsonInput"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 1000
                  }
                },
                "required": [
                  "messaging_account_id",
                  "name",
                  "categories"
                ]
              },
              "example": {
                "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                "name": "Agenda tu visita",
                "categories": [
                  "APPOINTMENT_BOOKING"
                ],
                "builder_state": {
                  "screens": [
                    {
                      "title": "¿Cuándo te acomoda?",
                      "footer_label": "Continuar",
                      "components": [
                        {
                          "kind": "heading",
                          "text": "Agenda tu visita"
                        },
                        {
                          "kind": "date_picker",
                          "name": "fecha",
                          "label": "Fecha preferida",
                          "required": true
                        }
                      ]
                    }
                  ]
                },
                "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The created flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "DRAFT",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": null,
                    "preview_expires_at": null,
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": false,
                    "message_body": null,
                    "message_cta": null,
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-flows/sync": {
      "post": {
        "summary": "Reconcile a channel's managed flows against Meta",
        "description": "The most destructive call on this router: local rows Meta no longer reports are deleted. Reports `{ checked, updated, removed }`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid"
                  }
                },
                "required": [
                  "messaging_account_id"
                ]
              },
              "example": {
                "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The reconcile counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "checked": 3,
                    "updated": 1,
                    "removed": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-flows/managed/{id}": {
      "get": {
        "summary": "Fetch one managed flow",
        "description": "The flow row, including its `builder_state` (the screen/component tree) and its `status` on Meta. **Health** — pushed by Meta’s `flows` webhook: `health_status` is `blocked` / `throttled` (Meta stopped delivering the flow — sends fail with 132068 / 132069), `degraded` (an endpoint alert is open: error rate, p90 latency, availability, or the Flow JSON version is about to freeze) or `healthy`. `active_alerts` holds the open alerts keyed `error_rate:endpoint` / `error_rate:client` / `latency` / `availability` / `version_expiry` (plus `status`, carrying Meta’s reason, while throttled or blocked); each alert is `{ type, source, state, value, threshold, window, meta_event, message, details, occurred_at }`. `last_alert` is the most recent health event of any kind, recoveries included; `health_updated_at` when it was applied.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The flow, including `builder_state` (or `flow_json`)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "PUBLISHED",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a draft flow",
        "description": "Only a `DRAFT` flow can be deleted (**409** otherwise) — deprecate a published one instead.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "No content"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is published and immutable"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a flow (drafts and published flows, in place)",
        "description": "Works on a `DRAFT` and on a `PUBLISHED` (or throttled / blocked) flow: the Flow JSON is re-uploaded to the SAME Meta flow, which Meta returns to `DRAFT` until `POST /whatsapp-flows/managed/{id}/publish` publishes it again — `status` mirrors what Meta reports after the edit. A `DEPRECATED` flow is final (**409**).\n\nA builder flow takes `builder_state`; a raw-JSON flow (`authoring: \"json\"`) takes `flow_json` and never a `builder_state` — convert a builder or external flow first with `POST /whatsapp-flows/managed/{id}/convert-to-json`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "SIGN_UP",
                        "SIGN_IN",
                        "APPOINTMENT_BOOKING",
                        "LEAD_GENERATION",
                        "CONTACT_US",
                        "CUSTOMER_SUPPORT",
                        "SURVEY",
                        "OTHER"
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 3
                  },
                  "builder_state": {
                    "type": "object",
                    "properties": {
                      "screens": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "footer_label": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 35
                            },
                            "footer_captions": {
                              "type": "object",
                              "properties": {
                                "left": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "center": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "right": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                }
                              },
                              "additionalProperties": false
                            },
                            "next_rules": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "when": {
                                    "$ref": "#/components/schemas/WhatsappFlowCondition"
                                  },
                                  "goto": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "maximum": 9
                                  }
                                },
                                "required": [
                                  "when",
                                  "goto"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 20
                            },
                            "default_next": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 9
                            },
                            "components": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/WhatsappFlowComponent"
                              },
                              "maxItems": 49
                            }
                          },
                          "required": [
                            "title",
                            "components"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 10
                      },
                      "summary_screen": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "screens"
                    ]
                  },
                  "flow_json": {
                    "$ref": "#/components/schemas/WhatsappFlowJsonInput"
                  }
                }
              },
              "example": {
                "name": "Agenda tu visita al showroom"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita al showroom",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "PUBLISHED",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is deprecated"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/settings": {
      "patch": {
        "summary": "Update a flow's agent settings",
        "description": "The Vitrina-only settings: `key` (lowercase slug, unique per WhatsApp number — skills and knowledge documents name the flow by it, in backticks), `description` (when the AI agent should send it), `ai_enabled` (the agent may send it while it is `PUBLISHED`) and the default interactive message (`message_body`, `message_cta`, optional `message_header` / `message_footer`). Every field is optional; `null` or a blank string clears one.\n\nNone of it is sent to Meta, so this works on a flow in **any** status — a published flow, and one created outside Vitrina, included.\n\nCompletion (ADR 0120): `completion_mode` — `agent` (the answers become a customer message and the AI agent answers; the default), `book_then_agent` (a form with an available-hours field books the chosen hour at submit, then the agent confirms), `book_only` (books at submit and sends `confirmation_message`; no AI turn unless the booking failed) or `automation_only` (no AI turn; only events, webhooks and automations). `field_map` maps form fields to `name`, `email`, `rut`, `phone` or `attr:<contact attribute key>` (replaces the whole map). `confirmation_message` takes `{{field}}` placeholders by field name and `{{cita}}` for the booked hour; blank = the default text.\n\n**409** `flow_key_taken` — another flow on the same number uses the key. **422** `flow_ai_incomplete` — `ai_enabled` without a description, message body and button label (checked after merging with what the flow already has); `flow_completion_mode_unsupported` — a booking mode on a form without an available-hours field; `flow_field_map_invalid` — the map names a field the form lacks.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "key": {
                    "type": "string",
                    "pattern": "^[a-z0-9][a-z0-9_]{0,63}$"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1000
                  },
                  "ai_enabled": {
                    "type": "boolean"
                  },
                  "message_body": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024
                  },
                  "message_cta": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 30
                  },
                  "message_header": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "message_footer": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "completion_mode": {
                    "type": "string",
                    "enum": [
                      "agent",
                      "book_then_agent",
                      "book_only",
                      "automation_only"
                    ]
                  },
                  "field_map": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "enum": [
                            "name",
                            "email",
                            "document",
                            "phone"
                          ]
                        },
                        {
                          "type": "string",
                          "enum": [
                            "rut"
                          ]
                        },
                        {
                          "type": "string",
                          "pattern": "^attr:[a-z0-9][a-z0-9_.-]{0,99}$"
                        }
                      ]
                    }
                  },
                  "confirmation_message": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 1024
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                "ai_enabled": true,
                "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                "message_cta": "Agendar visita"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The flow with its updated settings",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "PUBLISHED",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "`flow_key_taken` — the key is in use"
          },
          "422": {
            "description": "`flow_ai_incomplete` — switching the agent on needs a description, message body and button label; `flow_completion_mode_unsupported`; `flow_field_map_invalid`"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/test-send": {
      "post": {
        "summary": "Send a flow to a phone for testing",
        "description": "Sends the flow — a `DRAFT` included (`mode: draft`, its latest draft) — to `phone`, with a real session behind it: submitting it runs the flow's completion (booking, events, confirmation) like a customer's. When the phone has a live conversation on the flow's WhatsApp number the session is tied to it, so the reply lands there. WhatsApp only delivers inside the phone's open 24-hour window with the number — write to it first; Meta's refusal comes back as a **502** with a Spanish message saying what to do.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "phone": {
                    "type": "string",
                    "minLength": 8,
                    "maxLength": 24,
                    "pattern": "^\\+?[0-9 ()-]{8,24}$"
                  }
                },
                "required": [
                  "phone"
                ],
                "additionalProperties": false
              },
              "example": {
                "phone": "+56912345678"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What was sent",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "external_id": "wamid.HBgLNTY5MTIzNDU2NzgVAgARGBI",
                    "conversation_id": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5c",
                    "mode": "draft",
                    "session": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The flow is deprecated"
          },
          "422": {
            "description": "`flow_test_phone_invalid`"
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/publish": {
      "post": {
        "summary": "Publish a flow",
        "description": "Refused while `validation_errors` is non-empty. A published flow can still be edited in place (`PATCH`) and published again.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The published flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "PUBLISHED",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Blocked by outstanding validation_errors"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/deprecate": {
      "post": {
        "summary": "Deprecate a published flow",
        "description": "Stops it being offered as new; conversations already inside it are unaffected.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deprecated flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "DEPRECATED",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/whatsapp-flows/managed/{id}/duplicate": {
      "post": {
        "summary": "Duplicate a flow as a new draft",
        "description": "Copies the flow's `builder_state` (or a raw-JSON flow’s `flow_json`) and its agent and completion settings (except the AI switch) into a brand-new `DRAFT` flow (a new `meta_flow_id`), leaving the original untouched. `name` defaults to the original’s name with a suffix.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  }
                }
              },
              "example": {
                "name": "Agenda tu visita (copia)"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The duplicated draft",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita (copia)",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "DRAFT",
                    "authoring": "builder",
                    "builder_state": {
                      "screens": [
                        {
                          "title": "¿Cuándo te acomoda?",
                          "footer_label": "Continuar",
                          "components": [
                            {
                              "kind": "heading",
                              "text": "Agenda tu visita"
                            },
                            {
                              "kind": "date_picker",
                              "name": "fecha",
                              "label": "Fecha preferida",
                              "required": true
                            }
                          ]
                        }
                      ]
                    },
                    "flow_json": null,
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita_copia",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": false,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/preview": {
      "get": {
        "summary": "Get (or refresh) a flow preview link",
        "description": "Read-only and side-effect free by default — answers the cached preview URL. `?refresh=true` forces a fresh one through the bridge when the cached one has expired.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            },
            "required": false,
            "name": "refresh",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The preview link and its expiry",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "status": "PUBLISHED",
                    "validation_errors": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/data-preview": {
      "post": {
        "summary": "Preview the live data of one flow screen",
        "description": "For a flow with live data (a Dropdown / Radio / Checkbox with a `source`, or a DatePicker with `available_dates`): the screen data the data endpoint would answer for `screen_index` given `answers` so far — `<field>__options` lists, `<field>__min` / `__max` / `__unavailable` dates, carried answers and `<field>__value` (the selection a refresh keeps). Works on an unsaved `builder_state` and reads real availability, but never books or writes anything. `error_message` is the short customer-facing text WhatsApp would show (e.g. no hours left that day).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "messaging_account_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "builder_state": {
                    "type": "object",
                    "properties": {
                      "screens": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "title": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 80
                            },
                            "footer_label": {
                              "type": "string",
                              "minLength": 1,
                              "maxLength": 35
                            },
                            "footer_captions": {
                              "type": "object",
                              "properties": {
                                "left": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "center": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                },
                                "right": {
                                  "type": "string",
                                  "minLength": 1,
                                  "maxLength": 15
                                }
                              },
                              "additionalProperties": false
                            },
                            "next_rules": {
                              "type": "array",
                              "items": {
                                "type": "object",
                                "properties": {
                                  "when": {
                                    "$ref": "#/components/schemas/WhatsappFlowCondition"
                                  },
                                  "goto": {
                                    "type": "integer",
                                    "minimum": 1,
                                    "maximum": 9
                                  }
                                },
                                "required": [
                                  "when",
                                  "goto"
                                ],
                                "additionalProperties": false
                              },
                              "maxItems": 20
                            },
                            "default_next": {
                              "type": "integer",
                              "minimum": 1,
                              "maximum": 9
                            },
                            "components": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/WhatsappFlowComponent"
                              },
                              "maxItems": 49
                            }
                          },
                          "required": [
                            "title",
                            "components"
                          ]
                        },
                        "minItems": 1,
                        "maxItems": 10
                      },
                      "summary_screen": {
                        "type": "boolean"
                      }
                    },
                    "required": [
                      "screens"
                    ]
                  },
                  "screen_index": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10
                  },
                  "answers": {
                    "type": "object",
                    "additionalProperties": {
                      "anyOf": [
                        {
                          "type": "string",
                          "maxLength": 500
                        },
                        {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "maxLength": 500
                          },
                          "maxItems": 50
                        },
                        {
                          "type": "boolean"
                        },
                        {
                          "type": "number"
                        },
                        {
                          "type": "object",
                          "additionalProperties": {
                            "type": "string",
                            "maxLength": 20
                          }
                        }
                      ]
                    },
                    "default": {}
                  },
                  "context": {
                    "type": "object",
                    "properties": {
                      "subject": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "subject_label": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "subject_kind": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 40
                      },
                      "location": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "location_label": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "appointment_type": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "appointment_type_label": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "staff": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "staff_label": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "service": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "service_label": {
                        "type": [
                          "string",
                          "null"
                        ],
                        "maxLength": 300
                      },
                      "contact": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "properties": {
                          "name": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 300
                          },
                          "phone": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 300
                          },
                          "email": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 300
                          },
                          "document": {
                            "type": [
                              "string",
                              "null"
                            ],
                            "maxLength": 300
                          }
                        }
                      },
                      "contact_attrs": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "additionalProperties": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 300
                        }
                      },
                      "conversation_attrs": {
                        "type": [
                          "object",
                          "null"
                        ],
                        "additionalProperties": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "maxLength": 300
                        }
                      }
                    },
                    "additionalProperties": false
                  }
                },
                "required": [
                  "messaging_account_id",
                  "builder_state",
                  "screen_index"
                ]
              },
              "example": {
                "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                "screen_index": 1,
                "answers": {
                  "sucursal": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5b",
                  "fecha": "2026-10-06"
                },
                "builder_state": {
                  "screens": [
                    {
                      "title": "Tu visita",
                      "components": [
                        {
                          "kind": "dropdown",
                          "name": "sucursal",
                          "label": "Sucursal",
                          "required": true,
                          "options": [],
                          "source": {
                            "id": "branches"
                          }
                        }
                      ]
                    },
                    {
                      "title": "Fecha y hora",
                      "components": [
                        {
                          "kind": "date_picker",
                          "name": "fecha",
                          "label": "Fecha",
                          "required": true,
                          "source": {
                            "id": "available_dates",
                            "params": {
                              "branch": {
                                "field": "sucursal"
                              }
                            }
                          }
                        },
                        {
                          "kind": "dropdown",
                          "name": "hora",
                          "label": "Hora",
                          "required": true,
                          "options": [],
                          "source": {
                            "id": "available_slots",
                            "params": {
                              "date": {
                                "field": "fecha"
                              },
                              "branch": {
                                "field": "sucursal"
                              }
                            }
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The screen data, and `error_message` when there is one",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": {
                      "sucursal": "0190a3b2-5c1d-7e4f-8a9b-0c1d2e3f4a5b",
                      "fecha__min": "2026-10-05",
                      "fecha__max": "2026-10-19",
                      "fecha__unavailable": [
                        "2026-10-11",
                        "2026-10-18"
                      ],
                      "fecha__value": "2026-10-06",
                      "hora__options": [
                        {
                          "id": "2026-10-06T13:30:00.000Z",
                          "title": "mar 6 oct 10:30"
                        },
                        {
                          "id": "2026-10-06T14:30:00.000Z",
                          "title": "mar 6 oct 11:30"
                        }
                      ],
                      "hora__value": ""
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/whatsapp-flows/capabilities": {
      "get": {
        "summary": "What this workspace can build into a WhatsApp Flow",
        "description": "Discovery for anyone authoring a flow (UI, API, MCP): the Flow JSON and data API versions, the authoring modes, the builder palette (`components` — every `kind` with its props, types and bounds, read off the same schema create/update validate with), the live-data `data_sources` with their params and whether THIS workspace can answer them (booking engine + vertical aware; `available: false` carries the `reason`), the `context_keys` a field can be prefilled from (generic keys + the workspace's custom contact/conversation attributes), the `completion_modes` and whether booking at submit is possible with this workspace's engine, and the limits. Labels follow the workspace vertical (`subject_label`: vehicle, property, product, treatment, …). `messaging_account_id` is required.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "messaging_account_id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "The workspace's flow-building capabilities",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappFlowCapabilities"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "flow_json_version": "7.3",
                    "data_api_version": "4.0",
                    "authoring_modes": [
                      "builder",
                      "json"
                    ],
                    "vertical": "healthcare",
                    "subject_label": "Tratamiento",
                    "booking_engine": {
                      "key": "native-clinic",
                      "can_book": true,
                      "booking_link_only": false
                    },
                    "components": [
                      {
                        "kind": "date_picker",
                        "props": {
                          "name": {
                            "type": "string",
                            "required": true,
                            "max": 60,
                            "min": 1
                          },
                          "label": {
                            "type": "string",
                            "required": true,
                            "max": 40,
                            "min": 1
                          },
                          "required": {
                            "type": "boolean",
                            "required": false
                          },
                          "helper_text": {
                            "type": "string",
                            "required": false,
                            "max": 80
                          },
                          "source": {
                            "type": "object",
                            "required": false
                          }
                        }
                      }
                    ],
                    "data_sources": [
                      {
                        "source": "available_slots",
                        "kind": "options",
                        "label": "Horas disponibles",
                        "description": "Free hours on one day (`date` required) from the booking engine, after the booking-notice policy.",
                        "params": [
                          {
                            "name": "date",
                            "required": true
                          },
                          {
                            "name": "service",
                            "required": false
                          },
                          {
                            "name": "professional",
                            "required": false
                          }
                        ],
                        "available": true,
                        "reason": null
                      },
                      {
                        "source": "vehicles",
                        "kind": "options",
                        "label": "Tratamiento",
                        "description": "Items of the workspace inventory (today: the vehicle stock); `query` searches, `branch` narrows.",
                        "params": [
                          {
                            "name": "branch",
                            "required": false
                          },
                          {
                            "name": "query",
                            "required": false
                          }
                        ],
                        "available": false,
                        "reason": "this workspace's inventory is not exposed as a flow source yet"
                      }
                    ],
                    "context_keys": [
                      {
                        "key": "subject",
                        "label": "Tratamiento",
                        "source": "conversation"
                      },
                      {
                        "key": "contact.name",
                        "label": "Nombre del contacto",
                        "source": "contact"
                      },
                      {
                        "key": "contact_attr:prevision",
                        "label": "Previsión",
                        "source": "contact_attr"
                      }
                    ],
                    "completion_modes": [
                      {
                        "mode": "book_then_agent",
                        "label": "Agendar y que el agente confirme",
                        "description": "The appointment is booked (or held for a deposit) when the form is submitted; the agent then confirms and continues.",
                        "books_at_submit": true,
                        "available": true,
                        "reason": null
                      }
                    ],
                    "limits": {
                      "max_screens": 10,
                      "max_components_per_screen": 49,
                      "max_images_per_screen": 3,
                      "image_max_bytes": 307200,
                      "builder_flow_json_max_bytes": 1000000,
                      "raw_flow_json_max_bytes": 10000000
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/flow-json": {
      "get": {
        "summary": "Get a flow's current Flow JSON",
        "description": "Whatever its authoring: a raw-JSON flow answers its stored `flow_json` (`source: \"stored\"`), a builder flow the generator output its last save uploaded (`\"generated\"`), and a flow created outside Vitrina the flow.json asset downloaded from Meta (`\"meta\"`, through the bridge). Read-only.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "The Flow JSON and where it came from",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlowJson"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "authoring": "json",
                    "source": "stored",
                    "flow_json": "{\n  \"version\": \"7.3\",\n  \"screens\": [\n    {\n      \"id\": \"AGENDA\",\n      \"title\": \"Agenda tu visita\",\n      \"terminal\": true,\n      \"layout\": {\n        \"type\": \"SingleColumnLayout\",\n        \"children\": [\n          {\n            \"type\": \"Form\",\n            \"name\": \"form\",\n            \"children\": [\n              {\n                \"type\": \"DatePicker\",\n                \"name\": \"fecha\",\n                \"label\": \"Fecha preferida\",\n                \"required\": true\n              },\n              {\n                \"type\": \"Footer\",\n                \"label\": \"Enviar\",\n                \"on-click-action\": {\n                  \"name\": \"complete\",\n                  \"payload\": {\n                    \"fecha\": \"${form.fecha}\"\n                  }\n                }\n              }\n            ]\n          }\n        ]\n      }\n    }\n  ]\n}"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Meta’s flow.json asset could not be downloaded"
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/whatsapp-flows/managed/{id}/convert-to-json": {
      "post": {
        "summary": "Convert a flow to raw Flow JSON authoring",
        "description": "**One-way.** The flow becomes `authoring: \"json\"`: a builder flow keeps the Flow JSON the generator produced (its `builder_state` is dropped — there is no reverse parser), and a flow created outside Vitrina takes its current flow.json downloaded from Meta, which is what makes it editable here (`PATCH` with `flow_json`). Nothing is uploaded and the status is untouched; settings, completion and templates keep working. Already json → returned unchanged.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "WhatsApp Flows"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The converted flow",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WhatsappManagedFlow"
                    },
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "e3e3e3e3-0000-4000-8000-000000000001",
                    "messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
                    "meta_flow_id": "1200300400500600",
                    "name": "Agenda tu visita",
                    "categories": [
                      "APPOINTMENT_BOOKING"
                    ],
                    "status": "PUBLISHED",
                    "authoring": "json",
                    "builder_state": null,
                    "flow_json": "{\n  \"version\": \"7.3\",\n  \"screens\": [\n    {\n      \"id\": \"AGENDA\",\n      \"title\": \"Agenda tu visita\",\n      \"terminal\": true,\n      \"layout\": {\n        \"type\": \"SingleColumnLayout\",\n        \"children\": [\n          {\n            \"type\": \"Form\",\n            \"name\": \"form\",\n            \"children\": [\n              {\n                \"type\": \"DatePicker\",\n                \"name\": \"fecha\",\n                \"label\": \"Fecha preferida\",\n                \"required\": true\n              },\n              {\n                \"type\": \"Footer\",\n                \"label\": \"Enviar\",\n                \"on-click-action\": {\n                  \"name\": \"complete\",\n                  \"payload\": {\n                    \"fecha\": \"${form.fecha}\"\n                  }\n                }\n              }\n            ]\n          }\n        ]\n      }\n    }\n  ]\n}",
                    "validation_errors": null,
                    "preview_url": "https://business.facebook.com/wa/manage/flows/1200300400500600/preview/",
                    "preview_expires_at": "2026-09-15T19:20:00.000Z",
                    "key": "agenda_tu_visita",
                    "description": "Cuando el cliente quiere agendar una visita al showroom y todavía no ha dado fecha ni hora.",
                    "ai_enabled": true,
                    "message_body": "Elige el día y la hora que te acomoden para tu visita 👇",
                    "message_cta": "Agendar visita",
                    "message_header": null,
                    "message_footer": null,
                    "completion_mode": "agent",
                    "field_map": {},
                    "confirmation_message": null,
                    "health_status": "degraded",
                    "active_alerts": {
                      "latency": {
                        "type": "latency",
                        "source": null,
                        "state": "activated",
                        "value": 8000,
                        "threshold": 7000,
                        "window": "30m",
                        "meta_event": "ENDPOINT_LATENCY",
                        "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                        "details": {
                          "p50_latency": 500,
                          "requests_count": 34
                        },
                        "occurred_at": "2026-09-15T18:10:00.000Z"
                      }
                    },
                    "last_alert": {
                      "type": "latency",
                      "source": null,
                      "state": "activated",
                      "value": 8000,
                      "threshold": 7000,
                      "window": "30m",
                      "meta_event": "ENDPOINT_LATENCY",
                      "message": "Flow endpoint latency has reached the p90 threshold in the last 30 minutes.",
                      "details": {
                        "p50_latency": 500,
                        "requests_count": 34
                      },
                      "occurred_at": "2026-09-15T18:10:00.000Z"
                    },
                    "health_updated_at": "2026-09-15T18:10:02.000Z",
                    "created_at": "2026-01-10T13:00:00.000Z",
                    "updated_at": "2026-09-15T18:20:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Meta’s flow.json asset could not be downloaded"
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients": {
      "get": {
        "summary": "List clinic patients",
        "description": "`search` (min 2 chars) matches the name accent-insensitively, a normalized-RUT prefix, or a phone-digits substring. Rows carry `contact_id` — the linked CRM contact, or null.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "nombre",
                "rut",
                "phone",
                "email",
                "synced_at"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "required": false,
            "name": "dir",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic patients",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "b1b0b8de-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "rut": "11.111.111-1",
                        "nombre": "María José",
                        "apellidos": "Fuentes Lagos",
                        "email": "mj.fuentes@correo-ejemplo.cl",
                        "phone": "+56987654321",
                        "birthdate": "1990-04-12",
                        "nombre_social": null,
                        "prevision": "Fonasa",
                        "sexo": "F",
                        "numero_ficha": null,
                        "ciudad": null,
                        "region": null,
                        "comuna": "Maipú",
                        "direccion": null,
                        "description": null,
                        "enabled": true,
                        "synced_at": null,
                        "contact_id": null,
                        "created_at": "2026-09-22T22:21:14.311Z",
                        "updated_at": "2026-09-22T22:21:14.311Z"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 20
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "post": {
        "summary": "Create a native patient",
        "description": "Create one of the clinic's OWN (native) patients — `source` is 'native'. 409 `not_native_source` when the tenant's clinic source is not 'native'. At least one of nombre/apellidos/rut is required; `birthdate` is a YYYY-MM-DD calendar day.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "apellidos": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "birthdate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "nombre_social": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "prevision": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "numero_ficha": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "ciudad": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "region": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "comuna": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "direccion": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 240
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              },
              "example": {
                "nombre": "María José",
                "apellidos": "Fuentes Lagos",
                "rut": "11.111.111-1",
                "email": "mj.fuentes@correo-ejemplo.cl",
                "phone": "+56987654321",
                "birthdate": "1990-04-12",
                "prevision": "Fonasa",
                "sexo": "F",
                "comuna": "Maipú"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created patient",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b0b8de-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "rut": "11.111.111-1",
                    "nombre": "María José",
                    "apellidos": "Fuentes Lagos",
                    "email": "mj.fuentes@correo-ejemplo.cl",
                    "phone": "+56987654321",
                    "birthdate": "1990-04-12",
                    "nombre_social": null,
                    "prevision": "Fonasa",
                    "sexo": "F",
                    "numero_ficha": null,
                    "ciudad": null,
                    "region": null,
                    "comuna": "Maipú",
                    "direccion": null,
                    "description": null,
                    "enabled": true,
                    "synced_at": null,
                    "contact_id": null,
                    "created_at": "2026-09-22T22:21:14.311Z",
                    "updated_at": "2026-09-22T22:21:14.311Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/patients/{id}": {
      "get": {
        "summary": "Get a clinic patient",
        "description": "Also carries `origin`: the patient's Origen, Meta (with the ad, ad set and campaign) Orgánico (a linked contact with no ad touch) or Sin dato (`unknown`: no linked contact) — the attribution Vitrina Ads reports, through the patient's linked contact. `link` is the contact link (`contact_id`, `verified_method`, `verified_at`) plus `revoked_at` / `revoked_reason` / `revoked_method`: set while the automatic phone-link guard holds the link revoked (a minor, a number shared by several fichas, …) — it still books but no longer reveals the record, and `revoked_method` is what the link was before.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic patient",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b0b8de-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "rut": "11.111.111-1",
                    "nombre": "María José",
                    "apellidos": "Fuentes Lagos",
                    "email": "mj.fuentes@correo-ejemplo.cl",
                    "phone": "+56987654321",
                    "birthdate": "1990-04-12",
                    "nombre_social": null,
                    "prevision": "Fonasa",
                    "sexo": "F",
                    "numero_ficha": null,
                    "ciudad": null,
                    "region": null,
                    "comuna": "Maipú",
                    "direccion": null,
                    "description": null,
                    "enabled": true,
                    "synced_at": null,
                    "contact_id": null,
                    "created_at": "2026-09-22T22:21:14.311Z",
                    "updated_at": "2026-09-22T22:21:14.311Z",
                    "data": {},
                    "link": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      },
      "delete": {
        "summary": "Delete a native patient",
        "description": "Native → hard-deleted (its contact link survives with a null clinic_patient_id). Synced (vendor) → 400 `cannot_delete_synced`.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a patient",
        "description": "Edit a native patient. 400 `not_editable` on a synced (non-native) row — those are managed in the clinic system, not Vitrina.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "apellidos": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "birthdate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "nombre_social": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "prevision": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "numero_ficha": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "ciudad": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "region": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "comuna": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "direccion": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 240
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  }
                }
              },
              "example": {
                "prevision": "Isapre Consalud",
                "comuna": "Maipú",
                "numero_ficha": "44110"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated patient",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "b1b0b8de-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "rut": "11.111.111-1",
                    "nombre": "María José",
                    "apellidos": "Fuentes Lagos",
                    "email": "mj.fuentes@correo-ejemplo.cl",
                    "phone": "+56987654321",
                    "birthdate": "1990-04-12",
                    "nombre_social": null,
                    "prevision": "Isapre Consalud",
                    "sexo": "F",
                    "numero_ficha": "44110",
                    "ciudad": null,
                    "region": null,
                    "comuna": "Maipú",
                    "direccion": null,
                    "description": null,
                    "enabled": true,
                    "synced_at": null,
                    "contact_id": null,
                    "created_at": "2026-09-22T22:21:14.311Z",
                    "updated_at": "2026-09-22T22:21:17.194Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/patients/{id}/citas": {
      "get": {
        "summary": "A patient's mirrored citas, newest first",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Citas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "d93e8eec-0000-4000-8000-000000000001",
                        "display_id": "A-1",
                        "engine": "native",
                        "external_id": null,
                        "starts_at": "2026-10-01T13:00:00.000Z",
                        "ends_at": "2026-10-01T13:30:00.000Z",
                        "status": "confirmed",
                        "estado_nombre": null,
                        "profesional": "Ana Rojas Vidal",
                        "tratamiento": null,
                        "sucursal": "Sucursal Maipú",
                        "sillon": null,
                        "motivo": null,
                        "comentarios": null
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true,
        "description": "A patient's mirrored citas, newest first\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it."
      }
    },
    "/clinic/patients/{id}/attendance": {
      "get": {
        "summary": "A patient's attendance history",
        "description": "Attended / no-show / late-cancel / cancelled counts, the recent list, and the date of the last late notice — computed from the mirrored citas of ANY engine plus the CURRENT bucket of each cita's `clinic_appointment_status`. The late-cancel split uses the tenant's window (`settings.clinic_attendance.late_cancel_hours`, default 8); `late_cancel_hours` re-counts the same history against another one without changing any setting. `late_cancel` is a SUBSET of `cancelled` — never add the two. A past cita nobody closed is counted as `unknown`, never as attended. This endpoint has no opinion about fees.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 168
            },
            "required": false,
            "name": "late_cancel_hours",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 10
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Attendance history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "counts": {
                      "attended": 0,
                      "no_show": 0,
                      "late_cancel": 0,
                      "cancelled": 0,
                      "cancelled_notice_unknown": 0,
                      "unknown": 0
                    },
                    "closed_total": 0,
                    "no_show_rate": null,
                    "last_late_cancel_at": null,
                    "last_no_show_at": null,
                    "late_cancel_hours": 8,
                    "notice_basis": "cancellation_recorded",
                    "recent": [],
                    "truncated": false,
                    "patient": {
                      "id": "b1b0b8de-0000-4000-8000-000000000001",
                      "source": "native",
                      "nombre": "María José",
                      "apellidos": "Fuentes Lagos"
                    },
                    "matched_by": [
                      "contact",
                      "ficha"
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/packs": {
      "get": {
        "summary": "List pack definitions (the catalog)",
        "description": "What the clinic sells as a multi-session pack: session count, price (bigint whole CLP, null when not recorded — never $0), validity, cadence and freeze policy. Reservo \"Planes\" mirror in as `source: reservo`. `active` omitted returns every row.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic packs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "6acd6212-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "name": "Pack 4 controles de ortodoncia",
                        "description": null,
                        "session_count": 4,
                        "precio_clp": 96000,
                        "validity_days": 180,
                        "cadence_days": null,
                        "freeze_allowed": true,
                        "freeze_max_count": 1,
                        "transferable": false,
                        "per_session_forfeits_price": true,
                        "requires_full_prepayment": true,
                        "online_bookable": false,
                        "active": true,
                        "synced_at": null,
                        "items": [
                          {
                            "id": "3a7d9767-0000-4000-8000-000000000001",
                            "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                            "quantity": 4,
                            "source": "native"
                          }
                        ]
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Define a pack (the catalog row)",
        "description": "What the clinic sells as a multi-session product: sessions, price (bigint whole CLP), vigencia, cadencia, freeze policy and the prestaciones it covers. `items` is HETEROGENEOUS — a pack of 4 limpiezas + 2 controles is two entries, which is how \"tratamientos agrupados\" are expressed rather than built twice.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "session_count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999
                  },
                  "precio_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "validity_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 3650
                  },
                  "cadence_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 365
                  },
                  "freeze_allowed": {
                    "type": "boolean"
                  },
                  "freeze_max_count": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10
                  },
                  "transferable": {
                    "type": "boolean"
                  },
                  "per_session_forfeits_price": {
                    "type": "boolean"
                  },
                  "requires_full_prepayment": {
                    "type": "boolean"
                  },
                  "online_bookable": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 99,
                          "default": 1
                        }
                      },
                      "required": [
                        "clinic_service_id"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  }
                },
                "required": [
                  "name",
                  "session_count"
                ],
                "additionalProperties": false
              },
              "example": {
                "name": "Pack 4 controles de ortodoncia",
                "session_count": 4,
                "precio_clp": 96000,
                "validity_days": 180,
                "freeze_allowed": true,
                "freeze_max_count": 1,
                "items": [
                  {
                    "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                    "quantity": 4
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Clinic pack",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "6acd6212-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Pack 4 controles de ortodoncia",
                    "description": null,
                    "session_count": 4,
                    "precio_clp": 96000,
                    "validity_days": 180,
                    "cadence_days": null,
                    "freeze_allowed": true,
                    "freeze_max_count": 1,
                    "transferable": false,
                    "per_session_forfeits_price": true,
                    "requires_full_prepayment": true,
                    "online_bookable": false,
                    "active": true,
                    "synced_at": null,
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "quantity": 4,
                        "source": "native"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/packs/purchases/{id}/sessions": {
      "get": {
        "summary": "One pack purchase's session ledger",
        "description": "Consumptions AND reversals, oldest first, with the derived balance. A reversal is a ROW (never a delete), which is what makes the balance survive a voided session.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pack sessions",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "purchase_id": "d376ca29-0000-4000-8000-000000000001",
                      "name": "Pack 4 controles de ortodoncia",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "source": "native",
                      "purchased_at": "2026-09-22T22:21:14.782Z",
                      "expires_at": "2027-03-21",
                      "frozen_from": null,
                      "frozen_to": null,
                      "freeze_count": 0,
                      "stored_status": "active",
                      "precio_clp": 96000,
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                      "balance": {
                        "session_count": 4,
                        "consumed": 1,
                        "reversed": 0,
                        "remaining": 3,
                        "over_consumed": false,
                        "frozen": false,
                        "date_passed": false,
                        "days_to_expiry": 180,
                        "effective_status": "active"
                      },
                      "last_session_at": "2026-09-22T22:21:14.813Z",
                      "next_session_due_after": null
                    },
                    "sessions": [
                      {
                        "id": "fcbdc4c2-0000-4000-8000-000000000001",
                        "consumed_at": "2026-09-22T22:21:14.813Z",
                        "appointment_id": null,
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "professional_id": "18342d1b-0000-4000-8000-000000000001",
                        "reversal_of": null,
                        "note": "Control 1 de 4",
                        "source": "native"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/packs": {
      "get": {
        "summary": "A patient's pack balances",
        "description": "Remaining sessions and expiry per purchase, DERIVED from the session rows (never a stored counter) against the clinic's own calendar day. A FROZEN pack does not expire while frozen. `live_only=true` narrows to the purchases that can still be consumed.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "live_only",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Pack balances",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "patient": {
                      "id": "b1b0b8de-0000-4000-8000-000000000001",
                      "source": "native",
                      "nombre": "María José",
                      "apellidos": "Fuentes Lagos"
                    },
                    "today": "2026-09-22",
                    "timezone": "America/Santiago",
                    "packs": [
                      {
                        "purchase_id": "d376ca29-0000-4000-8000-000000000001",
                        "name": "Pack 4 controles de ortodoncia",
                        "pack_id": "6acd6212-0000-4000-8000-000000000001",
                        "source": "native",
                        "purchased_at": "2026-09-22T22:21:14.782Z",
                        "expires_at": "2027-04-20",
                        "frozen_from": null,
                        "frozen_to": null,
                        "freeze_count": 1,
                        "stored_status": "active",
                        "precio_clp": 96000,
                        "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                        "balance": {
                          "session_count": 4,
                          "consumed": 0,
                          "reversed": 1,
                          "remaining": 4,
                          "over_consumed": false,
                          "frozen": false,
                          "date_passed": false,
                          "days_to_expiry": 210,
                          "effective_status": "active"
                        },
                        "last_session_at": "2026-09-22T22:21:14.813Z",
                        "next_session_due_after": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/{id}": {
      "delete": {
        "summary": "Retire a pack",
        "description": "Deactivates; never deletes. A sold purchase still points at the row and the reports have to keep naming what was sold last year.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic pack",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6acd6212-0000-4000-8000-000000000002",
                    "source": "native",
                    "external_id": null,
                    "name": "Pack de prueba",
                    "description": null,
                    "session_count": 2,
                    "precio_clp": null,
                    "validity_days": null,
                    "cadence_days": null,
                    "freeze_allowed": false,
                    "freeze_max_count": 1,
                    "transferable": false,
                    "per_session_forfeits_price": true,
                    "requires_full_prepayment": true,
                    "online_bookable": false,
                    "active": false,
                    "synced_at": null,
                    "items": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Edit a pack",
        "description": "A MIRRORED pack (Reservo's Planes) keeps the vendor's name, price, session count and vigencia — those four are overwritten on the next sync, so editing them here is refused out loud. The POLICY columns (freeze rules, transferability, online bookability) are Vitrina-owned and editable on any row.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "session_count": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 999
                  },
                  "precio_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "validity_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 3650
                  },
                  "cadence_days": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 365
                  },
                  "freeze_allowed": {
                    "type": "boolean"
                  },
                  "freeze_max_count": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 10
                  },
                  "transferable": {
                    "type": "boolean"
                  },
                  "per_session_forfeits_price": {
                    "type": "boolean"
                  },
                  "requires_full_prepayment": {
                    "type": "boolean"
                  },
                  "online_bookable": {
                    "type": "boolean"
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "items": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "clinic_service_id": {
                          "type": "string",
                          "format": "uuid"
                        },
                        "quantity": {
                          "type": "integer",
                          "minimum": 1,
                          "maximum": 99,
                          "default": 1
                        }
                      },
                      "required": [
                        "clinic_service_id"
                      ],
                      "additionalProperties": false
                    },
                    "maxItems": 50
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "description": "Cuatro controles mensuales, válidos seis meses"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Clinic pack",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "6acd6212-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "name": "Pack 4 controles de ortodoncia",
                    "description": "Cuatro controles mensuales, válidos seis meses",
                    "session_count": 4,
                    "precio_clp": 96000,
                    "validity_days": 180,
                    "cadence_days": null,
                    "freeze_allowed": true,
                    "freeze_max_count": 1,
                    "transferable": false,
                    "per_session_forfeits_price": true,
                    "requires_full_prepayment": true,
                    "online_bookable": false,
                    "active": true,
                    "synced_at": null,
                    "items": [
                      {
                        "id": "3a7d9767-0000-4000-8000-000000000001",
                        "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                        "quantity": 4,
                        "source": "native"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases": {
      "post": {
        "summary": "Sell a pack to a patient",
        "description": "Writes BOTH halves of a pack: the ENTITLEMENT (sessions, vigencia resolved once as a date) here, and the MONEY as one `obligation_group` of kind `pack` plus one `pack` obligation in the payments ledger, linked by `obligation_group_id`. The price comes from the catalog, from the pricing engine (`use_quote`) or from an explicit `precio_clp` — which REQUIRES `price_override_reason`. The response carries the purchase, its DERIVED balance and its DERIVED ledger state; no total is stored anywhere.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "clinic_patient_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "contact_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "pack_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "session_count": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 999
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "precio_clp": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 999999999999
                  },
                  "price_override_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 3,
                    "maxLength": 500
                  },
                  "use_quote": {
                    "type": "boolean"
                  },
                  "expires_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "due_on": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                "pack_id": "6acd6212-0000-4000-8000-000000000001"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pack purchase",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "id": "d376ca29-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "budget_id": null,
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                      "name_snapshot": "Pack 4 controles de ortodoncia",
                      "session_count": 4,
                      "precio_clp": 96000,
                      "purchased_at": "2026-09-22T22:21:14.782Z",
                      "expires_at": "2027-03-21",
                      "frozen_from": null,
                      "frozen_to": null,
                      "freeze_count": 0,
                      "status": "active",
                      "cancelled_reason": null,
                      "price_source": "catalog",
                      "price_override_reason": null,
                      "price_explain": [],
                      "sold_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.781Z",
                      "updated_at": "2026-09-22T22:21:14.784Z"
                    },
                    "balance": {
                      "session_count": 4,
                      "consumed": 0,
                      "reversed": 0,
                      "remaining": 4,
                      "over_consumed": false,
                      "frozen": false,
                      "date_passed": false,
                      "days_to_expiry": 180,
                      "effective_status": "active"
                    },
                    "today": "2026-09-22",
                    "ledger": {
                      "id": "a067dce6-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "pack",
                      "label": "Pack 4 controles de ortodoncia",
                      "status": "open",
                      "obligation_count": 1,
                      "expected_clp": 96000,
                      "allocated_clp": 0,
                      "outstanding_clp": 96000,
                      "state": "pending",
                      "obligations": [
                        {
                          "id": "b44936d0-0000-4000-8000-000000000001",
                          "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                          "kind": "pack",
                          "direction": "receivable",
                          "label": "Pack 4 controles de ortodoncia",
                          "expected_clp": 96000,
                          "allocated_clp": 0,
                          "outstanding_clp": 96000,
                          "overpaid_clp": 0,
                          "state": "pending",
                          "provenance": null,
                          "is_overdue": false,
                          "status": "open",
                          "disposition": null,
                          "opened_on": "2026-09-22",
                          "due_on": null,
                          "closed_at": null,
                          "closed_reason": null,
                          "group_id": "a067dce6-0000-4000-8000-000000000001",
                          "source": "staff",
                          "document": null,
                          "allocations": []
                        }
                      ],
                      "created_at": "2026-09-22T22:21:14.760Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/packs/purchases/{id}": {
      "get": {
        "summary": "One pack purchase, with its balance and its ledger state",
        "description": "Sessions remaining derived from the session rows, money derived from the ledger. Nothing on this response is a stored total.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Pack purchase",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "id": "d376ca29-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "budget_id": null,
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                      "name_snapshot": "Pack 4 controles de ortodoncia",
                      "session_count": 4,
                      "precio_clp": 96000,
                      "purchased_at": "2026-09-22T22:21:14.782Z",
                      "expires_at": "2027-03-21",
                      "frozen_from": null,
                      "frozen_to": null,
                      "freeze_count": 0,
                      "status": "active",
                      "cancelled_reason": null,
                      "price_source": "catalog",
                      "price_override_reason": null,
                      "price_explain": [],
                      "sold_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.781Z",
                      "updated_at": "2026-09-22T22:21:14.784Z"
                    },
                    "balance": {
                      "session_count": 4,
                      "consumed": 0,
                      "reversed": 0,
                      "remaining": 4,
                      "over_consumed": false,
                      "frozen": false,
                      "date_passed": false,
                      "days_to_expiry": 180,
                      "effective_status": "active"
                    },
                    "today": "2026-09-22",
                    "ledger": {
                      "id": "a067dce6-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "pack",
                      "label": "Pack 4 controles de ortodoncia",
                      "status": "open",
                      "obligation_count": 1,
                      "expected_clp": 96000,
                      "allocated_clp": 0,
                      "outstanding_clp": 96000,
                      "state": "pending",
                      "obligations": [
                        {
                          "id": "b44936d0-0000-4000-8000-000000000001",
                          "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                          "kind": "pack",
                          "direction": "receivable",
                          "label": "Pack 4 controles de ortodoncia",
                          "expected_clp": 96000,
                          "allocated_clp": 0,
                          "outstanding_clp": 96000,
                          "overpaid_clp": 0,
                          "state": "pending",
                          "provenance": null,
                          "is_overdue": false,
                          "status": "open",
                          "disposition": null,
                          "opened_on": "2026-09-22",
                          "due_on": null,
                          "closed_at": null,
                          "closed_reason": null,
                          "group_id": "a067dce6-0000-4000-8000-000000000001",
                          "source": "staff",
                          "document": null,
                          "allocations": []
                        }
                      ],
                      "created_at": "2026-09-22T22:21:14.760Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases/{id}/consume": {
      "post": {
        "summary": "Consume one session",
        "description": "Appends a `clinic_pack_session` row. Refused when nothing remains, when the pack expired and when it is frozen — each of those three passable with a written `override_reason`, and none of them passable on a cancelled or refunded pack. One live consumption per (purchase, cita), enforced by a unique index: the same cita consumes once however many times it is closed.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "appointment_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "clinic_appointment_service_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "clinic_service_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "professional_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "consumed_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  },
                  "override_reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "minLength": 3,
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                "professional_id": "18342d1b-0000-4000-8000-000000000001",
                "note": "Control 1 de 4"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pack session",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "session": {
                      "id": "fcbdc4c2-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pack_purchase_id": "d376ca29-0000-4000-8000-000000000001",
                      "appointment_id": null,
                      "clinic_appointment_service_id": null,
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "consumed_at": "2026-09-22T22:21:14.813Z",
                      "reversal_of": null,
                      "note": "Control 1 de 4",
                      "override_reason": null,
                      "created_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.812Z"
                    },
                    "view": {
                      "purchase": {
                        "id": "d376ca29-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "pack_id": "6acd6212-0000-4000-8000-000000000001",
                        "budget_id": null,
                        "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                        "name_snapshot": "Pack 4 controles de ortodoncia",
                        "session_count": 4,
                        "precio_clp": 96000,
                        "purchased_at": "2026-09-22T22:21:14.782Z",
                        "expires_at": "2027-03-21",
                        "frozen_from": null,
                        "frozen_to": null,
                        "freeze_count": 0,
                        "status": "active",
                        "cancelled_reason": null,
                        "price_source": "catalog",
                        "price_override_reason": null,
                        "price_explain": [],
                        "sold_by": null,
                        "source": "native",
                        "external_id": null,
                        "data": {},
                        "synced_at": null,
                        "created_at": "2026-09-22T22:21:14.781Z",
                        "updated_at": "2026-09-22T22:21:14.784Z"
                      },
                      "balance": {
                        "session_count": 4,
                        "consumed": 1,
                        "reversed": 0,
                        "remaining": 3,
                        "over_consumed": false,
                        "frozen": false,
                        "date_passed": false,
                        "days_to_expiry": 180,
                        "effective_status": "active"
                      },
                      "today": "2026-09-22",
                      "ledger": {
                        "id": "a067dce6-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "pack",
                        "label": "Pack 4 controles de ortodoncia",
                        "status": "open",
                        "obligation_count": 1,
                        "expected_clp": 96000,
                        "allocated_clp": 0,
                        "outstanding_clp": 96000,
                        "state": "pending",
                        "obligations": [
                          {
                            "id": "b44936d0-0000-4000-8000-000000000001",
                            "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                            "kind": "pack",
                            "direction": "receivable",
                            "label": "Pack 4 controles de ortodoncia",
                            "expected_clp": 96000,
                            "allocated_clp": 0,
                            "outstanding_clp": 96000,
                            "overpaid_clp": 0,
                            "state": "pending",
                            "provenance": null,
                            "is_overdue": false,
                            "status": "open",
                            "disposition": null,
                            "opened_on": "2026-09-22",
                            "due_on": null,
                            "closed_at": null,
                            "closed_reason": null,
                            "group_id": "a067dce6-0000-4000-8000-000000000001",
                            "source": "staff",
                            "document": null,
                            "allocations": []
                          }
                        ],
                        "created_at": "2026-09-22T22:21:14.760Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases/{id}/sessions/{sessionId}/reverse": {
      "post": {
        "summary": "Undo a consumption",
        "description": "INSERTS the row that reverses it (`reversal_of`); never deletes. A consumption may be reversed at most once — a second reversal would credit the derived balance twice.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "sessionId",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "note": "Consumida por error"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Pack session reversal",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "session": {
                      "id": "fcbdc4c2-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "pack_purchase_id": "d376ca29-0000-4000-8000-000000000001",
                      "appointment_id": null,
                      "clinic_appointment_service_id": null,
                      "clinic_service_id": "4cf5bc59-0000-4000-8000-000000000001",
                      "professional_id": "18342d1b-0000-4000-8000-000000000001",
                      "consumed_at": "2026-09-22T22:21:14.833Z",
                      "reversal_of": "fcbdc4c2-0000-4000-8000-000000000001",
                      "note": "Consumida por error",
                      "override_reason": null,
                      "created_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.832Z"
                    },
                    "view": {
                      "purchase": {
                        "id": "d376ca29-0000-4000-8000-000000000001",
                        "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                        "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                        "pack_id": "6acd6212-0000-4000-8000-000000000001",
                        "budget_id": null,
                        "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                        "name_snapshot": "Pack 4 controles de ortodoncia",
                        "session_count": 4,
                        "precio_clp": 96000,
                        "purchased_at": "2026-09-22T22:21:14.782Z",
                        "expires_at": "2027-03-21",
                        "frozen_from": null,
                        "frozen_to": null,
                        "freeze_count": 0,
                        "status": "active",
                        "cancelled_reason": null,
                        "price_source": "catalog",
                        "price_override_reason": null,
                        "price_explain": [],
                        "sold_by": null,
                        "source": "native",
                        "external_id": null,
                        "data": {},
                        "synced_at": null,
                        "created_at": "2026-09-22T22:21:14.781Z",
                        "updated_at": "2026-09-22T22:21:14.784Z"
                      },
                      "balance": {
                        "session_count": 4,
                        "consumed": 0,
                        "reversed": 1,
                        "remaining": 4,
                        "over_consumed": false,
                        "frozen": false,
                        "date_passed": false,
                        "days_to_expiry": 180,
                        "effective_status": "active"
                      },
                      "today": "2026-09-22",
                      "ledger": {
                        "id": "a067dce6-0000-4000-8000-000000000001",
                        "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                        "kind": "pack",
                        "label": "Pack 4 controles de ortodoncia",
                        "status": "open",
                        "obligation_count": 1,
                        "expected_clp": 96000,
                        "allocated_clp": 0,
                        "outstanding_clp": 96000,
                        "state": "pending",
                        "obligations": [
                          {
                            "id": "b44936d0-0000-4000-8000-000000000001",
                            "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                            "kind": "pack",
                            "direction": "receivable",
                            "label": "Pack 4 controles de ortodoncia",
                            "expected_clp": 96000,
                            "allocated_clp": 0,
                            "outstanding_clp": 96000,
                            "overpaid_clp": 0,
                            "state": "pending",
                            "provenance": null,
                            "is_overdue": false,
                            "status": "open",
                            "disposition": null,
                            "opened_on": "2026-09-22",
                            "due_on": null,
                            "closed_at": null,
                            "closed_reason": null,
                            "group_id": "a067dce6-0000-4000-8000-000000000001",
                            "source": "staff",
                            "document": null,
                            "allocations": []
                          }
                        ],
                        "created_at": "2026-09-22T22:21:14.760Z"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases/{id}/freeze": {
      "post": {
        "summary": "Freeze a pack",
        "description": "Honours the catalog's `freeze_allowed` / `freeze_max_count` (a clinic's bridal package may be configured never to freeze). A CLOSED window pushes `expires_at` out by its own length immediately; an OPEN one («hasta nuevo aviso») pays out when it is lifted, because its length is not known until then.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "to": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "reason": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 500
                  }
                },
                "additionalProperties": false
              },
              "example": {
                "from": "2026-10-01",
                "to": "2026-10-31",
                "reason": "Viaje"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pack purchase",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "id": "d376ca29-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "budget_id": null,
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                      "name_snapshot": "Pack 4 controles de ortodoncia",
                      "session_count": 4,
                      "precio_clp": 96000,
                      "purchased_at": "2026-09-22T22:21:14.782Z",
                      "expires_at": "2027-04-20",
                      "frozen_from": "2026-10-01",
                      "frozen_to": "2026-10-31",
                      "freeze_count": 1,
                      "status": "frozen",
                      "cancelled_reason": null,
                      "price_source": "catalog",
                      "price_override_reason": null,
                      "price_explain": [],
                      "sold_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.781Z",
                      "updated_at": "2026-09-22T22:21:14.845Z"
                    },
                    "balance": {
                      "session_count": 4,
                      "consumed": 0,
                      "reversed": 1,
                      "remaining": 4,
                      "over_consumed": false,
                      "frozen": false,
                      "date_passed": false,
                      "days_to_expiry": 210,
                      "effective_status": "active"
                    },
                    "today": "2026-09-22",
                    "ledger": {
                      "id": "a067dce6-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "pack",
                      "label": "Pack 4 controles de ortodoncia",
                      "status": "open",
                      "obligation_count": 1,
                      "expected_clp": 96000,
                      "allocated_clp": 0,
                      "outstanding_clp": 96000,
                      "state": "pending",
                      "obligations": [
                        {
                          "id": "b44936d0-0000-4000-8000-000000000001",
                          "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                          "kind": "pack",
                          "direction": "receivable",
                          "label": "Pack 4 controles de ortodoncia",
                          "expected_clp": 96000,
                          "allocated_clp": 0,
                          "outstanding_clp": 96000,
                          "overpaid_clp": 0,
                          "state": "pending",
                          "provenance": null,
                          "is_overdue": false,
                          "status": "open",
                          "disposition": null,
                          "opened_on": "2026-09-22",
                          "due_on": null,
                          "closed_at": null,
                          "closed_reason": null,
                          "group_id": "a067dce6-0000-4000-8000-000000000001",
                          "source": "staff",
                          "document": null,
                          "allocations": []
                        }
                      ],
                      "created_at": "2026-09-22T22:21:14.760Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases/{id}/unfreeze": {
      "post": {
        "summary": "Lift a freeze",
        "description": "Closes an open window at today and pushes `expires_at` out by the days the pack actually spent frozen.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Pack purchase",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "id": "d376ca29-0000-4000-8000-000000000001",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "b1b0b8de-0000-4000-8000-000000000001",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "budget_id": null,
                      "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                      "name_snapshot": "Pack 4 controles de ortodoncia",
                      "session_count": 4,
                      "precio_clp": 96000,
                      "purchased_at": "2026-09-22T22:21:14.782Z",
                      "expires_at": "2027-04-20",
                      "frozen_from": null,
                      "frozen_to": null,
                      "freeze_count": 1,
                      "status": "active",
                      "cancelled_reason": null,
                      "price_source": "catalog",
                      "price_override_reason": null,
                      "price_explain": [],
                      "sold_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.781Z",
                      "updated_at": "2026-09-22T22:21:14.858Z"
                    },
                    "balance": {
                      "session_count": 4,
                      "consumed": 0,
                      "reversed": 1,
                      "remaining": 4,
                      "over_consumed": false,
                      "frozen": false,
                      "date_passed": false,
                      "days_to_expiry": 210,
                      "effective_status": "active"
                    },
                    "today": "2026-09-22",
                    "ledger": {
                      "id": "a067dce6-0000-4000-8000-000000000001",
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "kind": "pack",
                      "label": "Pack 4 controles de ortodoncia",
                      "status": "open",
                      "obligation_count": 1,
                      "expected_clp": 96000,
                      "allocated_clp": 0,
                      "outstanding_clp": 96000,
                      "state": "pending",
                      "obligations": [
                        {
                          "id": "b44936d0-0000-4000-8000-000000000001",
                          "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                          "kind": "pack",
                          "direction": "receivable",
                          "label": "Pack 4 controles de ortodoncia",
                          "expected_clp": 96000,
                          "allocated_clp": 0,
                          "outstanding_clp": 96000,
                          "overpaid_clp": 0,
                          "state": "pending",
                          "provenance": null,
                          "is_overdue": false,
                          "status": "open",
                          "disposition": null,
                          "opened_on": "2026-09-22",
                          "due_on": null,
                          "closed_at": null,
                          "closed_reason": null,
                          "group_id": "a067dce6-0000-4000-8000-000000000001",
                          "source": "staff",
                          "document": null,
                          "allocations": []
                        }
                      ],
                      "created_at": "2026-09-22T22:21:14.760Z"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/packs/purchases/{id}/cancel": {
      "post": {
        "summary": "Anular or refund a pack purchase",
        "description": "Marks the entitlement `cancelled` or `refunded` with a written reason. The session rows are NOT deleted (they are evidence of visits that happened) and no pesos move: voiding the money is the ledger's own act on the obligation.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Packs"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "minLength": 3,
                    "maxLength": 500
                  },
                  "refunded": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "reason"
                ],
                "additionalProperties": false
              },
              "example": {
                "reason": "El paciente desistió antes de la primera sesión",
                "refunded": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pack purchase",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "purchase": {
                      "id": "d376ca29-0000-4000-8000-000000000002",
                      "tenant_id": "a1a1a1a1-0000-4000-8000-000000000001",
                      "clinic_patient_id": "42db9ec9-0000-4000-8000-000000000001",
                      "pack_id": "6acd6212-0000-4000-8000-000000000001",
                      "budget_id": null,
                      "obligation_group_id": null,
                      "name_snapshot": "Pack 4 controles de ortodoncia",
                      "session_count": 4,
                      "precio_clp": 96000,
                      "purchased_at": "2026-09-22T22:21:14.882Z",
                      "expires_at": "2027-03-21",
                      "frozen_from": null,
                      "frozen_to": null,
                      "freeze_count": 0,
                      "status": "cancelled",
                      "cancelled_reason": "El paciente desistió antes de la primera sesión",
                      "price_source": "catalog",
                      "price_override_reason": null,
                      "price_explain": [],
                      "sold_by": null,
                      "source": "native",
                      "external_id": null,
                      "data": {},
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.881Z",
                      "updated_at": "2026-09-22T22:21:14.888Z"
                    },
                    "balance": {
                      "session_count": 4,
                      "consumed": 0,
                      "reversed": 0,
                      "remaining": 4,
                      "over_consumed": false,
                      "frozen": false,
                      "date_passed": false,
                      "days_to_expiry": 180,
                      "effective_status": "cancelled"
                    },
                    "today": "2026-09-22",
                    "ledger": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      }
    },
    "/clinic/patients/{id}/treatment-progress": {
      "get": {
        "summary": "A patient's treatment progress",
        "description": "Two sources, both reported: pack-backed progress where the clinic sells packs, and performed / unclosed / upcoming citas per service off the mirror for a clinic that does not (Medilink). Reports what was PERFORMED; it never invents the session count quoted at a consultation.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Budgets"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Treatment progress",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "patient": {
                      "id": "b1b0b8de-0000-4000-8000-000000000001",
                      "source": "native",
                      "nombre": "María José",
                      "apellidos": "Fuentes Lagos"
                    },
                    "today": "2026-09-22",
                    "timezone": "America/Santiago",
                    "packs": [
                      {
                        "purchase_id": "d376ca29-0000-4000-8000-000000000001",
                        "name": "Pack 4 controles de ortodoncia",
                        "pack_id": "6acd6212-0000-4000-8000-000000000001",
                        "source": "native",
                        "purchased_at": "2026-09-22T22:21:14.782Z",
                        "expires_at": "2027-04-20",
                        "frozen_from": null,
                        "frozen_to": null,
                        "freeze_count": 1,
                        "stored_status": "active",
                        "precio_clp": 96000,
                        "obligation_group_id": "a067dce6-0000-4000-8000-000000000001",
                        "balance": {
                          "session_count": 4,
                          "consumed": 0,
                          "reversed": 1,
                          "remaining": 4,
                          "over_consumed": false,
                          "frozen": false,
                          "date_passed": false,
                          "days_to_expiry": 210,
                          "effective_status": "active"
                        },
                        "last_session_at": "2026-09-22T22:21:14.813Z",
                        "next_session_due_after": null
                      }
                    ],
                    "services": [],
                    "truncated": false
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/patients/{id}/history": {
      "get": {
        "summary": "A patient's clinical history (Reservo)",
        "description": "The vendor's atenciones timeline — recetas and fichas clínicas, newest first, each with the record's stripped text — read LIVE off the Reservo session (never mirrored). Reservo-backed patients only: other sources answer `supported: false` with no entries.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Record"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinical history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "supported": false,
                    "entries": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    },
    "/clinic/booking-roster": {
      "get": {
        "summary": "«Nueva cita» pickers from Vitrina (professionals + visit types)",
        "description": "The active professionals and the bookable visit types of the clinic, read from Vitrina's own tables for every engine (never the vendor live). `synced_at` says how fresh the vendor roster is. Ids are the ones each engine's booking takes (see the schema).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "responses": {
          "200": {
            "description": "Booking roster",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRoster"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "engine": "healthatom",
                    "synced_at": "2026-10-23T12:00:00.000Z",
                    "professionals": [
                      {
                        "id": "b6a1c2d3-0000-4000-8000-000000000001",
                        "external_id": "12",
                        "nombre": "Dra. Camila Rojas",
                        "especialidad": "Ortodoncia",
                        "especialidades": [
                          "Ortodoncia"
                        ],
                        "sucursal_ids": [
                          "1"
                        ],
                        "intervalo_minutes": 30,
                        "agenda_online": true
                      }
                    ],
                    "services": [
                      {
                        "id": "c7b2d3e4-0000-4000-8000-000000000002",
                        "kind": "motivo",
                        "external_id": "501",
                        "nombre": "Evaluación de ortodoncia",
                        "categoria": "Ortodoncia",
                        "especialidad": "Ortodoncia",
                        "duracion": 30,
                        "profesionales": [
                          "12"
                        ],
                        "duraciones": {
                          "12": 30
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/booking-roster/refresh": {
      "post": {
        "summary": "Re-read the vendor's roster now («Actualizar»)",
        "description": "HealthAtom: re-reads the professionals roster now (`done`). Reservo: queues the account sync (`queued`). Native, or a refresh in the last minute: `not_needed`. Always answers with the roster as it stands.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic"
        ],
        "responses": {
          "200": {
            "description": "Refresh outcome and the roster",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/BookingRosterRefresh"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "status": "done",
                    "roster": {
                      "engine": "healthatom",
                      "synced_at": "2026-10-23T12:00:00.000Z",
                      "professionals": [
                        {
                          "id": "b6a1c2d3-0000-4000-8000-000000000001",
                          "external_id": "12",
                          "nombre": "Dra. Camila Rojas",
                          "especialidad": "Ortodoncia",
                          "especialidades": [
                            "Ortodoncia"
                          ],
                          "sucursal_ids": [
                            "1"
                          ],
                          "intervalo_minutes": 30,
                          "agenda_online": true
                        }
                      ],
                      "services": [
                        {
                          "id": "c7b2d3e4-0000-4000-8000-000000000002",
                          "kind": "motivo",
                          "external_id": "501",
                          "nombre": "Evaluación de ortodoncia",
                          "categoria": "Ortodoncia",
                          "especialidad": "Ortodoncia",
                          "duracion": 30,
                          "profesionales": [
                            "12"
                          ],
                          "duraciones": {
                            "12": 30
                          }
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/professionals": {
      "get": {
        "summary": "List the clinic's professionals",
        "description": "`search` (min 2 chars) matches the name accent-insensitively OR the primary/secondary specialties. `active` is OPTIONAL and omitting it returns EVERY row, active and retired — a professional who left still names mirrored citas. Sorted by `sort`/`dir` (default: display name asc; also especialidad, intervalo, synced_at). `schedule` is null when the weekly-hours read has not landed, which does NOT mean the professional works no days.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 160
            },
            "required": false,
            "name": "especialidad",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "nombre",
                "especialidad",
                "intervalo",
                "synced_at"
              ],
              "default": "nombre"
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "required": false,
            "name": "dir",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic professionals",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ClinicProfessional"
                          }
                        },
                        "total": {
                          "type": "number"
                        },
                        "page": {
                          "type": "number"
                        },
                        "limit": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "data",
                        "total",
                        "page",
                        "limit"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "18342d1b-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "nombre": "Ana",
                        "apellidos": "Rojas Vidal",
                        "rut": "12.345.678-5",
                        "email": "ana.rojas@clinica-ejemplo.cl",
                        "phone": null,
                        "especialidad": "Ortodoncia",
                        "especialidades_secundarias": [],
                        "cargo": null,
                        "registro": "SIS 123456",
                        "sexo": null,
                        "birthdate": null,
                        "direccion": null,
                        "agenda_online": true,
                        "intervalo_minutes": 30,
                        "active": true,
                        "description": null,
                        "schedule": null,
                        "synced_at": null,
                        "created_at": "2026-09-22T22:21:14.494Z",
                        "updated_at": "2026-09-22T22:21:14.494Z"
                      }
                    ],
                    "total": 1,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a native professional",
        "description": "Create one of the clinic's OWN (native) professionals — `source` is 'native', `active` is true. 409 `not_native_source` when the tenant's clinic source is not 'native' (activate native booking first). `schedule` is the weekly-hours editor payload (mon..sun → [{inicio,fin}], HH:MM).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "apellidos": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "especialidad": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "especialidades_secundarias": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 50
                  },
                  "cargo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "registro": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "birthdate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "direccion": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 240
                  },
                  "agenda_online": {
                    "type": "boolean"
                  },
                  "intervalo_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 600
                  },
                  "schedule": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "mon": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "tue": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "wed": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "thu": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "fri": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "sat": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "sun": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      }
                    }
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "charges_with": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "company_invoice",
                      "fee_receipt"
                    ],
                    "description": "«Cobra con» — how the professional charges the clinic for her liquidación: `company_invoice` = factura de su sociedad (then `company_legal_name` and a check-digit-valid `company_rut` are required), `fee_receipt` = boleta de honorarios (the liquidación then shows the SII retención of the boleta’s year and the líquido). Null = not recorded. Vitrina-owned: editable on any source; switching away from `company_invoice` clears the sociedad."
                  },
                  "company_legal_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "company_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  }
                },
                "required": [
                  "nombre"
                ]
              },
              "example": {
                "nombre": "Ana",
                "apellidos": "Rojas Vidal",
                "rut": "12.345.678-5",
                "email": "ana.rojas@clinica-ejemplo.cl",
                "especialidad": "Ortodoncia",
                "registro": "SIS 123456",
                "agenda_online": true,
                "intervalo_minutes": 30
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created professional",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicProfessional"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "18342d1b-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Ana",
                    "apellidos": "Rojas Vidal",
                    "rut": "12.345.678-5",
                    "email": "ana.rojas@clinica-ejemplo.cl",
                    "phone": null,
                    "especialidad": "Ortodoncia",
                    "especialidades_secundarias": [],
                    "cargo": null,
                    "registro": "SIS 123456",
                    "sexo": null,
                    "birthdate": null,
                    "direccion": null,
                    "agenda_online": true,
                    "intervalo_minutes": 30,
                    "active": true,
                    "description": null,
                    "schedule": null,
                    "synced_at": null,
                    "created_at": "2026-09-22T22:21:14.494Z",
                    "updated_at": "2026-09-22T22:21:14.494Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/professionals/{id}": {
      "get": {
        "summary": "Get one clinic professional",
        "description": "The list view plus the raw vendor payload (`data`) and `citas_next_7d` — the live (pending/confirmed) citas this professional holds over the next seven days, counted off the mirrored shadow rows.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic professional",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicProfessionalDetail"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "18342d1b-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Ana",
                    "apellidos": "Rojas Vidal",
                    "rut": "12.345.678-5",
                    "email": "ana.rojas@clinica-ejemplo.cl",
                    "phone": null,
                    "especialidad": "Ortodoncia",
                    "especialidades_secundarias": [],
                    "cargo": null,
                    "registro": "SIS 123456",
                    "sexo": null,
                    "birthdate": null,
                    "direccion": null,
                    "agenda_online": true,
                    "intervalo_minutes": 30,
                    "active": true,
                    "description": null,
                    "schedule": null,
                    "synced_at": null,
                    "created_at": "2026-09-22T22:21:14.494Z",
                    "updated_at": "2026-09-22T22:21:14.494Z",
                    "data": {},
                    "citas_next_7d": 0
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "delete": {
        "summary": "Delete a native professional",
        "description": "Native → hard-deleted. Synced (vendor) → 400 `cannot_delete_synced`; deactivate it with PATCH `active:false` instead (mirrored citas still name it).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true
      },
      "patch": {
        "summary": "Update a professional",
        "description": "Edit a professional (covers activar/desactivar via `active`). `description` is Vitrina-owned and editable on ANY source (native or synced mirror); every OTHER field returns 400 `not_editable` on a synced (non-native) row — those are managed in the clinic system, not Vitrina.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  },
                  "apellidos": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "phone": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "especialidad": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 160
                  },
                  "especialidades_secundarias": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 50
                  },
                  "cargo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "registro": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 80
                  },
                  "sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 40
                  },
                  "birthdate": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "direccion": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 240
                  },
                  "agenda_online": {
                    "type": "boolean"
                  },
                  "intervalo_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 600
                  },
                  "schedule": {
                    "type": [
                      "object",
                      "null"
                    ],
                    "properties": {
                      "mon": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "tue": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "wed": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "thu": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "fri": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "sat": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      },
                      "sun": {
                        "type": "array",
                        "items": {
                          "type": "object",
                          "properties": {
                            "inicio": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            },
                            "fin": {
                              "type": "string",
                              "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$"
                            }
                          },
                          "required": [
                            "inicio",
                            "fin"
                          ]
                        }
                      }
                    }
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 2000
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "charges_with": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "company_invoice",
                      "fee_receipt"
                    ],
                    "description": "«Cobra con» — how the professional charges the clinic for her liquidación: `company_invoice` = factura de su sociedad (then `company_legal_name` and a check-digit-valid `company_rut` are required), `fee_receipt` = boleta de honorarios (the liquidación then shows the SII retención of the boleta’s year and the líquido). Null = not recorded. Vitrina-owned: editable on any source; switching away from `company_invoice` clears the sociedad."
                  },
                  "company_legal_name": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 200
                  },
                  "company_rut": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 20
                  },
                  "user_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid",
                    "description": "The workspace member who IS this professional (a member of this workspace, or null to unlink). Decides whose clinical records a member on an own-patients role opens. Vitrina-owned: editable on any source. Requires `clinic_admin:write`."
                  }
                }
              },
              "example": {
                "cargo": "Ortodoncista",
                "especialidades_secundarias": [
                  "Odontopediatría"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated professional",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicProfessional"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "18342d1b-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Ana",
                    "apellidos": "Rojas Vidal",
                    "rut": "12.345.678-5",
                    "email": "ana.rojas@clinica-ejemplo.cl",
                    "phone": null,
                    "especialidad": "Ortodoncia",
                    "especialidades_secundarias": [
                      "Odontopediatría"
                    ],
                    "cargo": "Ortodoncista",
                    "registro": "SIS 123456",
                    "sexo": null,
                    "birthdate": null,
                    "direccion": null,
                    "agenda_online": true,
                    "intervalo_minutes": 30,
                    "active": true,
                    "description": null,
                    "schedule": null,
                    "synced_at": null,
                    "created_at": "2026-09-22T22:21:14.494Z",
                    "updated_at": "2026-09-22T22:21:14.527Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/services": {
      "get": {
        "summary": "List the clinic's services",
        "description": "The clinic's price list. `search` (min 2 chars) matches nombre, código or categoría accent-insensitively. `active` is OPTIONAL and omitting it returns every row. `precio` is the vendor LIST price before previsión/convenio — it is deliberately never shown to the AI agent. `is_entry` filters entry prestaciones, tri-state: `true` = entry (evaluación / diagnóstico), `false` = CONFIRMED treatment rows only, `unclassified` = nobody has said yet (`is_entry IS NULL`); omitted = every row. `arancel` (repeatable or comma-separated) keeps only rows priced in those vendor aranceles (`'0'` = «Arancel base»); each row carries `arancel_external_id` + `arancel_name`. `categoria` keeps one exact category. `agent_bookable` (`yes` | `no`) filters on the EFFECTIVE agent-bookable value: each row carries the explicit `agent_bookable` (null = undecided) and `agent_bookable_effective` (`agent_bookable ?? settings.clinic_attendance.agent_bookable_default`, unset default = true).\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 120
            },
            "required": false,
            "name": "search",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "active",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "pending",
                "stale",
                "draft",
                "reviewed"
              ]
            },
            "required": false,
            "name": "review_state",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false",
                "unclassified"
              ]
            },
            "required": false,
            "name": "is_entry",
            "in": "query"
          },
          {
            "schema": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string",
                "maxLength": 64
              },
              "maxItems": 1000
            },
            "required": false,
            "name": "arancel",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 120
            },
            "required": false,
            "name": "categoria",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "yes",
                "no"
              ]
            },
            "required": false,
            "name": "agent_bookable",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "nombre",
                "codigo",
                "categoria",
                "tipo",
                "precio",
                "synced_at"
              ]
            },
            "required": false,
            "name": "sort",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            },
            "required": false,
            "name": "dir",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "required": false,
            "name": "page",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Clinic services",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ClinicService"
                          }
                        },
                        "total": {
                          "type": "number"
                        },
                        "page": {
                          "type": "number"
                        },
                        "limit": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "data",
                        "total",
                        "page",
                        "limit"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "data": [
                      {
                        "id": "4cf5bc59-0000-4000-8000-000000000001",
                        "source": "native",
                        "external_id": null,
                        "nombre": "Control de ortodoncia",
                        "codigo": "ORT-CTRL",
                        "categoria": "Ortodoncia",
                        "tipo": null,
                        "precio": 25000,
                        "arancel": null,
                        "duration_minutes": 30,
                        "vendor_duration_minutes": null,
                        "review_state": "draft",
                        "reviewed_at": null,
                        "reviewed_by": null,
                        "is_telehealth": false,
                        "online_bookable": true,
                        "requires_consent_template_id": null,
                        "eligibility_min_age_years": null,
                        "eligibility_max_age_years": null,
                        "eligibility_sexo": null,
                        "eligibility_previsiones": null,
                        "active": true,
                        "synced_at": null
                      },
                      {
                        "id": "4cf5bc59-0000-4000-8000-000000000002",
                        "source": "native",
                        "external_id": null,
                        "nombre": "Limpieza dental",
                        "codigo": null,
                        "categoria": "Ortodoncia",
                        "tipo": null,
                        "precio": 35000,
                        "arancel": null,
                        "duration_minutes": 45,
                        "vendor_duration_minutes": null,
                        "review_state": "draft",
                        "reviewed_at": null,
                        "reviewed_by": null,
                        "is_telehealth": false,
                        "online_bookable": false,
                        "requires_consent_template_id": null,
                        "eligibility_min_age_years": null,
                        "eligibility_max_age_years": null,
                        "eligibility_sexo": null,
                        "eligibility_previsiones": null,
                        "active": true,
                        "synced_at": null
                      }
                    ],
                    "total": 2,
                    "page": 1,
                    "limit": 50
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      },
      "post": {
        "summary": "Create a native service",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "codigo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "categoria": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "tipo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "precio": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "duration_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 1440
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "is_telehealth": {
                    "type": "boolean"
                  },
                  "online_bookable": {
                    "type": "boolean"
                  },
                  "is_entry": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "agent_bookable": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "lab_bearing": {
                    "type": "boolean"
                  },
                  "requires_consent_template_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "eligibility_min_age_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 120
                  },
                  "eligibility_max_age_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 120
                  },
                  "eligibility_sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "F",
                      "M",
                      "otro"
                    ]
                  },
                  "eligibility_previsiones": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 30
                  }
                },
                "required": [
                  "nombre"
                ]
              },
              "example": {
                "nombre": "Control de ortodoncia",
                "codigo": "ORT-CTRL",
                "categoria": "Ortodoncia",
                "precio": 25000,
                "duration_minutes": 30,
                "online_bookable": true
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "ClinicService",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "4cf5bc59-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Control de ortodoncia",
                    "codigo": "ORT-CTRL",
                    "categoria": "Ortodoncia",
                    "tipo": null,
                    "precio": 25000,
                    "arancel": null,
                    "duration_minutes": 30,
                    "vendor_duration_minutes": null,
                    "review_state": "draft",
                    "reviewed_at": null,
                    "reviewed_by": null,
                    "is_telehealth": false,
                    "online_bookable": true,
                    "requires_consent_template_id": null,
                    "eligibility_min_age_years": null,
                    "eligibility_max_age_years": null,
                    "eligibility_sexo": null,
                    "eligibility_previsiones": null,
                    "active": true,
                    "synced_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "description": "Create a native service\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/services/aranceles": {
      "get": {
        "summary": "The clinic's vendor aranceles (price lists)",
        "description": "One entry per distinct `arancel_external_id` in the mirrored catálogo, with `total` rows and `active` rows, largest active list first. Dentalink/Medilink keep one copy of every prestación per arancel; this is what the screen filters on and what `tenant.settings.clinic_catalog_aranceles` (the aranceles the agent quotes) is chosen from. Empty for Reservo / native catálogos.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "responses": {
          "200": {
            "description": "Aranceles with row counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClinicServiceArancel"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": [
                    {
                      "external_id": "0",
                      "name": "Arancel base",
                      "total": 412,
                      "active": 390
                    },
                    {
                      "external_id": "652",
                      "name": "Arancel convenio",
                      "total": 652,
                      "active": 640
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/motivos": {
      "get": {
        "summary": "List the clinic’s motivos de atención",
        "description": "The «motivos de atención» mirrored from Dentalink/Medilink — the clinic system’s own bookable visit kinds per especialidad, each with its length and the professionals who attend it. `vendor_*` is what the clinic system said at the last resync; `duration_minutes` / `active` / the professional set are the effective values, which follow it unless `overrides` names them. `booking_catalog` says whether the AI agent and «Nueva cita» book against these or against the prestaciones.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "responses": {
          "200": {
            "description": "Motivos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicMotivoList"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "booking_catalog": "motivos",
                    "product": "dentalink",
                    "last_synced_at": "2026-10-22T18:00:00.000Z",
                    "last_error": null,
                    "motivos": [
                      {
                        "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
                        "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
                        "source": "healthatom",
                        "external_id": "23",
                        "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
                        "especialidad": "General",
                        "vendor_duration_minutes": 45,
                        "duration_minutes": 60,
                        "vendor_active": true,
                        "active": true,
                        "agent_bookable": true,
                        "color": "#cccccc",
                        "agenda_online": false,
                        "overrides": [
                          "duration_minutes"
                        ],
                        "synced_at": "2026-10-22T18:00:00.000Z",
                        "created_at": "2026-10-22T18:00:00.000Z",
                        "updated_at": "2026-10-22T18:05:00.000Z",
                        "professionals": [
                          {
                            "external_id": "8",
                            "nombre": "Catalina Lima",
                            "vendor_duration_minutes": 45,
                            "duration_minutes": 60,
                            "active": true
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/motivos/resync": {
      "post": {
        "summary": "Resync motivos de atención from the clinic system",
        "description": "Reads the clinic system now and merges it in: new motivos are added, the rest refresh, and every field the clinic changed in Vitrina (`overrides`) is KEPT and listed in `kept` with both values. Motivos the clinic system no longer lists land in `removed` (deactivated, not deleted). Synchronous — about one second per professional.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "responses": {
          "200": {
            "description": "Resync report",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicMotivoResyncResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "total": 32,
                    "synced_at": "2026-10-22T18:00:00.000Z",
                    "added": [
                      {
                        "id": null,
                        "nombre": "Blanqueamiento",
                        "especialidad": "General"
                      }
                    ],
                    "updated": [],
                    "removed": [],
                    "kept": [
                      {
                        "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
                        "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
                        "especialidad": "General",
                        "field": "duration_minutes",
                        "vendor": "45 min",
                        "vitrina": "60 min"
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/motivos/agent-bookable": {
      "post": {
        "summary": "Set which motivos the AI agent may book, in bulk",
        "description": "«¿Ofrecer servicio?» — Vitrina’s own flag, never sent to the clinic system and never touched by a resync.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": "boolean"
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                },
                "required": [
                  "value",
                  "ids"
                ]
              },
              "example": {
                "value": false,
                "ids": [
                  "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rows updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "updated": {
                          "type": "integer",
                          "minimum": 0
                        }
                      },
                      "required": [
                        "updated"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "updated": 1
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/motivos/{id}": {
      "patch": {
        "summary": "Edit a motivo de atención",
        "description": "A `duration_minutes` or `active` set here becomes an override: every later resync keeps it (and reports it). A length set to exactly the clinic system’s own figure is not an override. `reset` hands fields back to the clinic system. A length set here applies to every professional; otherwise a booking uses the professional’s own length from the clinic system.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 5,
                    "maximum": 1440
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "agent_bookable": {
                    "type": "boolean"
                  },
                  "reset": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "duration_minutes",
                        "active",
                        "professionals"
                      ]
                    },
                    "maxItems": 3
                  }
                }
              },
              "example": {
                "duration_minutes": 60
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The motivo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicMotivo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
                    "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
                    "source": "healthatom",
                    "external_id": "23",
                    "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
                    "especialidad": "General",
                    "vendor_duration_minutes": 45,
                    "duration_minutes": 60,
                    "vendor_active": true,
                    "active": true,
                    "agent_bookable": true,
                    "color": "#cccccc",
                    "agenda_online": false,
                    "overrides": [
                      "duration_minutes"
                    ],
                    "synced_at": "2026-10-22T18:00:00.000Z",
                    "created_at": "2026-10-22T18:00:00.000Z",
                    "updated_at": "2026-10-22T18:05:00.000Z",
                    "professionals": [
                      {
                        "external_id": "8",
                        "nombre": "Catalina Lima",
                        "vendor_duration_minutes": 45,
                        "duration_minutes": 60,
                        "active": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/motivos/{id}/professionals": {
      "put": {
        "summary": "Choose who attends a motivo",
        "description": "Replaces the professionals who attend the motivo (by the clinic system’s professional id). An override: resyncs keep it until `reset: [\"professionals\"]`.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "professionals": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "maxItems": 500
                  }
                },
                "required": [
                  "professionals"
                ]
              },
              "example": {
                "professionals": [
                  "8",
                  "19"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The motivo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicMotivo"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "id": "0194e7a0-1c2b-7d3e-8f40-5a6b7c8d9e01",
                    "tenant_id": "0194e7a0-0000-7000-8000-000000000001",
                    "source": "healthatom",
                    "external_id": "23",
                    "nombre": "DIAGNOSTICO INTEGRAL ODONTOLOGIA",
                    "especialidad": "General",
                    "vendor_duration_minutes": 45,
                    "duration_minutes": 60,
                    "vendor_active": true,
                    "active": true,
                    "agent_bookable": true,
                    "color": "#cccccc",
                    "agenda_online": false,
                    "overrides": [
                      "duration_minutes"
                    ],
                    "synced_at": "2026-10-22T18:00:00.000Z",
                    "created_at": "2026-10-22T18:00:00.000Z",
                    "updated_at": "2026-10-22T18:05:00.000Z",
                    "professionals": [
                      {
                        "external_id": "8",
                        "nombre": "Catalina Lima",
                        "vendor_duration_minutes": 45,
                        "duration_minutes": 60,
                        "active": true
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/booking-catalog": {
      "put": {
        "summary": "Choose what the agent and «Nueva cita» book against",
        "description": "`motivos` (the clinic system’s motivos de atención) or `prestaciones` (the price list). `motivos` needs at least one synced motivo.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "catalog": {
                    "type": "string",
                    "enum": [
                      "motivos",
                      "prestaciones"
                    ]
                  }
                },
                "required": [
                  "catalog"
                ]
              },
              "example": {
                "catalog": "motivos"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Saved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "catalog": {
                          "type": "string",
                          "enum": [
                            "motivos",
                            "prestaciones"
                          ]
                        }
                      },
                      "required": [
                        "catalog"
                      ]
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "catalog": "motivos"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/services/agent-bookable-count": {
      "get": {
        "summary": "How many active prestaciones the AI agent may book",
        "description": "`{ bookable, reception_only, total }` over ACTIVE services, by the EFFECTIVE agent-bookable value (`agent_bookable`, or the tenant default `settings.clinic_attendance.agent_bookable_default` when the row is undecided). `arancel` (repeatable or comma-separated) counts only rows priced in those vendor aranceles.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "string",
                "maxLength": 64
              },
              "maxItems": 1000
            },
            "required": false,
            "name": "arancel",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Agent-bookable split",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicServiceAgentBookableCount"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "bookable": 12,
                    "reception_only": 383,
                    "total": 395
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta"
      }
    },
    "/clinic/services/agent-bookable": {
      "post": {
        "summary": "Set which prestaciones the AI agent may book, in bulk",
        "description": "Sets `agent_bookable` to `value` — `true` (the agent may offer and book it), `false` (reception-only: the agent knows it exists, tells the patient reception schedules it and never books it) or `null` (back to the tenant default) — on up to 500 `ids`, OR on every row matching `all_matching` (the same filters, in the same string form, as the `GET /clinic/services` query). Exactly one of the two. Works on ANY source — the flag is Vitrina-owned and never reaches the vendor. `updated` is how many rows matched; ids of another tenant match nothing.\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "value": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  },
                  "all_matching": {
                    "$ref": "#/components/schemas/ClinicServiceMatchFilter"
                  }
                },
                "required": [
                  "value"
                ]
              },
              "example": {
                "value": false,
                "all_matching": {
                  "search": "control",
                  "active": "true",
                  "arancel": "0"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Rows updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ClinicServiceEntryFlagsResult"
                    }
                  },
                  "required": [
                    "data"
                  ]
                },
                "example": {
                  "data": {
                    "updated": 37
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ]
      }
    },
    "/clinic/services/{id}": {
      "delete": {
        "summary": "Delete a native service",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Delete a native service\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Edit a native service (or activar/desactivar)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "codigo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 60
                  },
                  "categoria": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "tipo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "maxLength": 120
                  },
                  "precio": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 1000000000
                  },
                  "duration_minutes": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 1,
                    "maximum": 1440
                  },
                  "active": {
                    "type": "boolean"
                  },
                  "is_telehealth": {
                    "type": "boolean"
                  },
                  "online_bookable": {
                    "type": "boolean"
                  },
                  "is_entry": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "agent_bookable": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "lab_bearing": {
                    "type": "boolean"
                  },
                  "requires_consent_template_id": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "uuid"
                  },
                  "eligibility_min_age_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 120
                  },
                  "eligibility_max_age_years": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "maximum": 120
                  },
                  "eligibility_sexo": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "F",
                      "M",
                      "otro"
                    ]
                  },
                  "eligibility_previsiones": {
                    "type": [
                      "array",
                      "null"
                    ],
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 120
                    },
                    "maxItems": 30
                  }
                }
              },
              "example": {
                "precio": 28000
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "ClinicService",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "4cf5bc59-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Control de ortodoncia",
                    "codigo": "ORT-CTRL",
                    "categoria": "Ortodoncia",
                    "tipo": null,
                    "precio": 28000,
                    "arancel": null,
                    "duration_minutes": 30,
                    "vendor_duration_minutes": null,
                    "review_state": "draft",
                    "reviewed_at": null,
                    "reviewed_by": null,
                    "is_telehealth": false,
                    "online_bookable": true,
                    "requires_consent_template_id": null,
                    "eligibility_min_age_years": null,
                    "eligibility_max_age_years": null,
                    "eligibility_sexo": null,
                    "eligibility_previsiones": null,
                    "active": true,
                    "synced_at": null
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Edit a native service (or activar/desactivar)\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/service-categories": {
      "get": {
        "summary": "List the clinic's service categories",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "enabled",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Service categories",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "795cf9e4-0000-4000-8000-000000000001",
                      "source": "native",
                      "nombre": "Ortodoncia",
                      "enabled": true,
                      "created_at": "2026-09-22T22:21:14.461Z",
                      "updated_at": "2026-09-22T22:21:14.461Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "List the clinic's service categories\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "post": {
        "summary": "Create a service category (find-or-create, native only)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  }
                },
                "required": [
                  "nombre"
                ]
              },
              "example": {
                "nombre": "Ortodoncia"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Service category",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "795cf9e4-0000-4000-8000-000000000001",
                    "source": "native",
                    "nombre": "Ortodoncia",
                    "enabled": true,
                    "created_at": "2026-09-22T22:21:14.461Z",
                    "updated_at": "2026-09-22T22:21:14.461Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "description": "Create a service category (find-or-create, native only)\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/service-categories/{id}": {
      "delete": {
        "summary": "Delete a service category",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Delete a service category\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Enable/disable a service category",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Services"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Service category",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "795cf9e4-0000-4000-8000-000000000001",
                    "source": "native",
                    "nombre": "Ortodoncia",
                    "enabled": true,
                    "created_at": "2026-09-22T22:21:14.461Z",
                    "updated_at": "2026-09-22T22:21:14.476Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Enable/disable a service category\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/specialties": {
      "get": {
        "summary": "List the clinic's specialties",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ]
            },
            "required": false,
            "name": "enabled",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Specialties",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "69762225-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "nombre": "Ortodoncia",
                      "enabled": true,
                      "is_editable": true,
                      "synced_at": null,
                      "created_at": "2026-09-22T22:21:14.433Z",
                      "updated_at": "2026-09-22T22:21:14.433Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "List the clinic's specialties\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "post": {
        "summary": "Create a specialty (find-or-create, native only)",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nombre": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 160
                  }
                },
                "required": [
                  "nombre"
                ]
              },
              "example": {
                "nombre": "Ortodoncia"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Specialty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {}
                  }
                },
                "example": {
                  "data": {
                    "id": "69762225-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Ortodoncia",
                    "enabled": true,
                    "is_editable": true,
                    "synced_at": null,
                    "created_at": "2026-09-22T22:21:14.433Z",
                    "updated_at": "2026-09-22T22:21:14.433Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Replay-safe retries: resending the SAME key with the SAME body returns the original response (`X-Idempotent-Replay: 1`) instead of creating a second copy — safe to send whenever a response might not have arrived. The same key with a DIFFERENT body answers `409 IDEMPOTENCY_KEY_CONFLICT`; use a fresh key per operation.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 200,
              "pattern": "^[A-Za-z0-9_-]+$"
            }
          }
        ],
        "description": "Create a specialty (find-or-create, native only)\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/specialties/{id}": {
      "delete": {
        "summary": "Delete a specialty",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-destructive": true,
        "description": "Delete a specialty\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      },
      "patch": {
        "summary": "Enable/disable a specialty",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Professionals"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "path"
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "enabled"
                ]
              },
              "example": {
                "enabled": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Specialty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "69762225-0000-4000-8000-000000000001",
                    "source": "native",
                    "external_id": null,
                    "nombre": "Ortodoncia",
                    "enabled": true,
                    "is_editable": true,
                    "synced_at": null,
                    "created_at": "2026-09-22T22:21:14.433Z",
                    "updated_at": "2026-09-22T22:21:14.447Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "description": "Enable/disable a specialty\n\n**Connected apps:** every patient and contact in the response is a Seudónimo de paciente — initials plus a stable number, `\"M.F. · #1001\"` — unless the clinic allowed patient names, with RUT, phone and email masked in free text and `meta.patient_privacy` saying so. Clinical alerts (`flags`) are withheld in both modes, and a non-JSON body (an export, a file) is refused with `403 CONNECTED_APP_SENSITIVE_DATA`."
      }
    },
    "/clinic/contacts/{contactId}/patient": {
      "get": {
        "summary": "The contact page's Paciente card",
        "description": "The linked patient plus its next and last cita. 404 `not_linked` when the contact is not a patient.\n\n**Connected apps:** refused with `403 CONNECTED_APP_SENSITIVE_DATA`, whatever scopes they hold — this operation carries a dato sensible (ADR 0106 §4). Only an API key or a personal token of the workspace that holds the scope reaches it.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "tags": [
          "Clinic Patients"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "contactId",
            "in": "path"
          }
        ],
        "responses": {
          "200": {
            "description": "Paciente card",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {},
                    "meta": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  }
                },
                "example": {
                  "data": {
                    "patient": {
                      "id": "b1b0b8de-0000-4000-8000-000000000001",
                      "source": "native",
                      "external_id": null,
                      "rut": "11.111.111-1",
                      "nombre": "María José",
                      "apellidos": "Fuentes Lagos",
                      "email": "mj.fuentes@correo-ejemplo.cl",
                      "phone": "+56987654321",
                      "birthdate": "1990-04-12",
                      "nombre_social": null,
                      "prevision": "Isapre Consalud",
                      "sexo": "F",
                      "numero_ficha": "44110",
                      "ciudad": null,
                      "region": null,
                      "comuna": "Maipú",
                      "direccion": null,
                      "description": null,
                      "enabled": true,
                      "synced_at": null,
                      "contact_id": "1a73af9e-0000-4000-8000-000000000001",
                      "created_at": "2026-09-22T22:21:14.311Z",
                      "updated_at": "2026-09-22T22:21:17.194Z"
                    },
                    "next_cita": {
                      "id": "d93e8eec-0000-4000-8000-000000000001",
                      "display_id": "A-1",
                      "engine": "native",
                      "external_id": null,
                      "starts_at": "2026-10-01T13:00:00.000Z",
                      "ends_at": "2026-10-01T13:30:00.000Z",
                      "status": "confirmed",
                      "estado_nombre": null,
                      "profesional": "Ana Rojas Vidal",
                      "tratamiento": null,
                      "sucursal": "Sucursal Maipú",
                      "sillon": null,
                      "motivo": null,
                      "comentarios": null
                    },
                    "last_cita": null,
                    "attendance": {
                      "completed": 0,
                      "no_show": 0,
                      "cancelled": 0
                    },
                    "recent_citas": [
                      {
                        "id": "d93e8eec-0000-4000-8000-000000000001",
                        "display_id": "A-1",
                        "engine": "native",
                        "external_id": null,
                        "starts_at": "2026-10-01T13:00:00.000Z",
                        "ends_at": "2026-10-01T13:30:00.000Z",
                        "status": "confirmed",
                        "estado_nombre": null,
                        "profesional": "Ana Rojas Vidal",
                        "tratamiento": null,
                        "sucursal": "Sucursal Maipú",
                        "sillon": null,
                        "motivo": null,
                        "comentarios": null
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Conflict (incl. Idempotency-Key reuse with different body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-vitrina-tier": "beta",
        "x-vitrina-sensitive": true
      }
    }
  },
  "webhooks": {}
}
